Change-Id: Ia38ffba8ac4da1fbd3a51d34dc6a6bb60982567a
This commit is contained in:
elpaablo
2025-10-02 14:41:40 +01:00
committed by osm1019
parent 767f5db7f8
commit 3877d92c04
50 changed files with 4040 additions and 30 deletions

View File

@@ -19,7 +19,18 @@ android_app {
"org.lineageos.platform.internal",
],
required: [
"privapp-permissions-device_settings.xml",
],
optimize: {
proguard_flags_files: ["proguard.flags"],
},
}
prebuilt_etc {
name: "privapp-permissions-device_settings.xml",
src: "privapp-permissions-device_settings.xml",
sub_dir: "permissions",
system_ext_specific: true,
}

View File

@@ -26,6 +26,9 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="lineageos.permission.HARDWARE_ABSTRACTION_ACCESS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<protected-broadcast android:name="org.lineageos.device.DeviceSettings.UPDATE_SLIDER_SETTINGS" />
<protected-broadcast android:name="org.lineageos.device.DeviceSettings.UPDATE_SLIDER_POSITION" />
@@ -35,13 +38,23 @@
android:label="@string/device_title"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
android:theme="@style/Theme.SubSettingsBase.Expressive"
android:theme="@style/Theme.DeviceSettings"
android:exported="true">
<receiver android:name=".BootCompletedReceiver"
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>
<!-- Device Settings -->
<activity
android:name=".DeviceSettingsActivity"
android:label="@string/device_title"
android:exported="true">
<intent-filter>
<action android:name="com.android.settings.action.EXTRA_SETTINGS" />
@@ -88,5 +101,74 @@
<action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
</intent-filter>
</provider>
<!-- BypassCharging activity -->
<activity android:name=".bypasschrg.BypassChargingActivity"
android:label="@string/bypass_charging_title"
android:exported="true">
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/bypass_charging_summary" />
</activity>
<!-- BypassChargingTile service -->
<service
android:name=".bypasschrg.BypassChargingTile"
android:label="@string/bypass_charging_title"
android:icon="@drawable/ic_bypass_charging"
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>
<!-- TileHandler activity -->
<activity
android:name=".TileHandlerActivity"
android:exported="true"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</activity>
<!-- GameBar Overlay -->
<activity
android:name=".gamebar.GameBarSettingsActivity"
android:label="@string/game_bar_title"
android:exported="true">
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/game_bar_summary" />
</activity>
<!-- GameBar AppSelector -->
<activity android:name=".gamebar.GameBarAppSelectorActivity" />
<activity android:name=".gamebar.GameBarAppRemoverActivity" />
<!-- GameBar Overlay Tile Service -->
<service
android:name=".gamebar.GameBarTileService"
android:label="@string/game_bar_tile_label"
android:icon="@drawable/ic_game_bar"
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 Overlay Monitor Service -->
<service
android:name=".gamebar.GameBarMonitorService"
android:exported="false" />
</application>
</manifest>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017-2019 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.
-->
<permissions>
<privapp-permissions package="org.lineageos.device.DeviceSettings">
<permission name="android.permission.RECEIVE_BOOT_COMPLETED"/>
</privapp-permissions>
</permissions>

View File

@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:startColor="@android:color/transparent"
android:endColor="#CC000000"
android:angle="270"/>
</shape>

View File

@@ -0,0 +1,27 @@
<!--
Copyright (C) 2024 The Android Open Source 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:fillType="nonZero"
android:pathData="M8,6C8.552,6 9,6.448 9,7C9,7.552 8.552,8 8,8L4,8L4,16L7,16C7.552,16 8,16.448 8,17C8,17.552 7.552,18 7,18L4,18C2.895,18 2,17.105 2,16L2,8C2,6.895 2.895,6 4,6L8,6ZM18,6C19.105,6 20,6.895 20,8L20,9C21.105,9 22,9.895 22,11L22,13C22,14.105 21.105,15 20,15L20,16C20,17.105 19.105,18 18,18L14,18C13.448,18 13,17.552 13,17C13,16.448 13.448,16 14,16L18,16L18,8L15,8C14.448,8 14,7.552 14,7C14,6.448 14.448,6 15,6L18,6ZM12.514,6.143C12.988,6.427 13.142,7.041 12.858,7.515L10.766,11L12.982,11C13.767,11 14.252,11.856 13.848,12.53L10.858,17.514C10.573,17.988 9.959,18.142 9.486,17.858C9.012,17.573 8.858,16.959 9.143,16.486L11.234,13L9.018,13C8.233,13 7.748,12.144 8.152,11.47L11.142,6.486C11.427,6.012 12.041,5.858 12.514,6.143Z" />
</vector>

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

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/bypass_charging_fragment"
android:name="org.lineageos.device.DeviceSettings.bypasschrg.BypassChargingFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

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_bar_fragment"
android:name="org.lineageos.device.DeviceSettings.gamebar.GameBarFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -0,0 +1,22 @@
<?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="240dp">
<ImageView
android:id="@+id/bannerImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"
android:src="@drawable/oplus_banner" />
<!-- Fade overlay at the bottom -->
<View
android:id="@+id/bannerFadeOverlay"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_gravity="bottom"
android:background="@drawable/banner_fade_gradient"/>
</FrameLayout>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DeviceSettingsActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="240dp"
android:background="@android:color/transparent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:contentScrim="@android:color/transparent"
app:toolbarId="@id/action_bar">
<ImageView
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/oplus_banner"
android:fitsSystemWindows="true"
android:scaleType="fitCenter"
app:layout_collapseMode="parallax" />
<Toolbar
android:id="@+id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="72dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

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_bar_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#80000000"
android:padding="8dp"
android:orientation="vertical">
</LinearLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/app_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/app_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/app_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="8dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="16sp" />
<TextView
android:id="@+id/app_package"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -16,8 +16,8 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="device_title">Настройки OnePlus</string>
<string name="device_summary">Изменение особых настроек устройства OnePlus</string>
<string name="device_title">Aston Settings</string>
<string name="device_summary">Extra settings for OnePlus Ace3 | 12R</string>
<!-- Notification slider -->
<string name="notification_slider_category_title">Переключатель режима</string>
@@ -69,4 +69,26 @@
<string name="edge_touch_title">Снять ограничения касаний</string>
<string name="edge_touch_summary">Улучшение в играх нескольких касаний</string>
<string name="edge_touch_warning">Может увеличить вероятность случайного прикосновения</string>
<!-- Gaming category -->
<string name="gaming_category_title">Gaming</string>
<string name="gaming_category_summary">Extra settings for gamers</string>
<!-- Bypass charging strings -->
<string name="bypass_charging_title">Bypass charging</string>
<string name="bypass_charging_summary">Supply power directly to the phone</string>
<string name="bypass_charging_info_title">Bypass charging supplies power directly to the board, instead of the battery, helping to reduce device heat, when running demanding applications like games.</string>
<string name="bypass_charging_unavailable">Bypass charging is not supported on this device</string>
<string name="bypass_charging_enabled">Enabled</string>
<string name="bypass_charging_disabled">Disabled</string>
<string name="bypass_charging_error">Coudn\'t enable Bypass Charging.</string>
<!-- GameBar Overlay -->
<string name="game_bar_title">GameBar</string>
<string name="game_bar_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_bar_tile_label">GameBar</string>
<string name="game_bar_tile_description">Toggle the game overlay</string>
</resources>

View File

@@ -111,4 +111,104 @@
<item>64</item>
<item>63</item>
</string-array>
<!-- FPS Overlay -->
<string-array name="game_bar_fps_method_entries">
<item>New API (Default)</item>
<item>Legacy Sysfs</item>
</string-array>
<string-array name="game_bar_fps_method_values">
<item>new</item>
<item>legacy</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_bar_format_entries">
<item>Full</item>
<item>Minimal</item>
</string-array>
<string-array name="game_bar_format_values">
<item>full</item>
<item>minimal</item>
</string-array>
<!-- Split Mode -->
<string-array name="game_bar_split_mode_entries">
<item>Side-by-Side</item>
<item>Stacked</item>
</string-array>
<string-array name="game_bar_split_mode_values">
<item>side_by_side</item>
<item>stacked</item>
</string-array>
<!-- Long press timeouts -->
<string-array name="game_bar_longpress_entries">
<item>1 second</item>
<item>3 seconds</item>
<item>5 seconds</item>
<item>10 seconds</item>
</string-array>
<string-array name="game_bar_longpress_values">
<item>1000</item>
<item>3000</item>
<item>5000</item>
<item>10000</item>
</string-array>
</resources>

View File

@@ -16,8 +16,8 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="device_title">OnePlus Settings</string>
<string name="device_summary">Adjust OnePlus specific device settings</string>
<string name="device_title">Aston Settings</string>
<string name="device_summary">Extra settings for OnePlus Ace3 | 12R</string>
<!-- Notification slider -->
<string name="notification_slider_category_title">Alert slider</string>
@@ -69,4 +69,26 @@
<string name="edge_touch_title">Unlimit edge touch</string>
<string name="edge_touch_summary">Improve your multi-touch experience in games</string>
<string name="edge_touch_warning">May increase the possibility of accidental touch</string>
<!-- Gaming category -->
<string name="gaming_category_title">Gaming</string>
<string name="gaming_category_summary">Extra settings for gamers</string>
<!-- Bypass charging strings -->
<string name="bypass_charging_title">Bypass charging</string>
<string name="bypass_charging_summary">Supply power directly to the phone</string>
<string name="bypass_charging_info_title">Bypass charging supplies power directly to the board, instead of the battery, helping to reduce device heat, when running demanding applications like games.</string>
<string name="bypass_charging_unavailable">Bypass charging is not supported on this device</string>
<string name="bypass_charging_enabled">Enabled</string>
<string name="bypass_charging_disabled">Disabled</string>
<string name="bypass_charging_error">Coudn\'t enable Bypass Charging.</string>
<!-- GameBar Overlay -->
<string name="game_bar_title">GameBar</string>
<string name="game_bar_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_bar_tile_label">GameBar</string>
<string name="game_bar_tile_description">Toggle the game overlay</string>
</resources>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source 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.
-->
<resources>
<!-- Using in SubSettings page including injected settings page -->
<style name="Theme.DeviceSettings" parent="@style/Theme.SubSettingsBase.Expressive">
<!-- Suppress the built-in action bar -->
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreferenceCompat
android:key="bypass_charging"
android:title="@string/bypass_charging_title"
android:icon="@drawable/ic_bypass_charging"
android:summary="@string/bypass_charging_summary"
android:persistent="false" />
<com.android.settingslib.widget.FooterPreference
android:key="bypass_charging_info"
android:title="@string/bypass_charging_info_title"
android:selectable="false" />
</PreferenceScreen>

View File

@@ -0,0 +1,252 @@
<?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">
<com.android.settingslib.widget.MainSwitchPreference
android:key="game_bar_enable"
android:title="Enable GameBar Overlay"
android:summary="@string/game_bar_summary"
android:defaultValue="false" />
<PreferenceCategory
android:title="Overlay Features"
android:dependency="game_bar_enable">
<SwitchPreferenceCompat
android:key="game_bar_fps_enable"
android:title="FPS Overlay"
android:summary="Show current FPS on screen"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_temp_enable"
android:title="Device Temperature"
android:summary="Show device (battery) temperature"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_cpu_usage_enable"
android:title="CPU Usage"
android:summary="Show current CPU usage percentage"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_cpu_clock_enable"
android:title="CPU Clock Speeds"
android:summary="Show current CPU clock speeds for each core"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_cpu_temp_enable"
android:title="CPU Temperature"
android:summary="Show CPU temperature (thermal_zone0)"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_ram_enable"
android:title="RAM Usage"
android:summary="Show current RAM usage in MB"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_gpu_usage_enable"
android:title="GPU Usage"
android:summary="Show GPU usage percentage"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_gpu_clock_enable"
android:title="GPU Clock Speed"
android:summary="Show current GPU clock frequency"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_gpu_temp_enable"
android:title="GPU Temperature"
android:summary="Show current GPU temperature"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory
android:title="FPS Measurement Method"
android:dependency="game_bar_fps_enable">
<ListPreference
android:key="game_bar_fps_method"
android:title="Select FPS Method"
android:summary="Choose between the New API method (default) and Legacy Sysfs"
android:defaultValue="new"
android:entries="@array/game_bar_fps_method_entries"
android:entryValues="@array/game_bar_fps_method_values" />
</PreferenceCategory>
<PreferenceCategory
android:title="Customization"
android:dependency="game_bar_enable">
<SeekBarPreference
android:key="game_bar_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_bar_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_bar_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_bar_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_bar_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_bar_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_bar_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_bar_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_bar_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_bar_format"
android:title="Overlay Format"
android:summary="Choose between Full or Minimal display"
android:defaultValue="full"
android:entries="@array/game_bar_format_entries"
android:entryValues="@array/game_bar_format_values" />
</PreferenceCategory>
<PreferenceCategory
android:title="Split Config"
android:dependency="game_bar_enable">
<ListPreference
android:key="game_bar_split_mode"
android:title="Split Mode"
android:summary="Choose Side-by-Side or Stacked arrangement"
android:defaultValue="stacked"
android:entries="@array/game_bar_split_mode_entries"
android:entryValues="@array/game_bar_split_mode_values" />
</PreferenceCategory>
<PreferenceCategory
android:title="Overlay Gesture Controls"
android:dependency="game_bar_enable">
<SwitchPreferenceCompat
android:key="game_bar_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" />
<SwitchPreferenceCompat
android:key="game_bar_doubletap_capture"
android:title="Enable Double Tap to Capture"
android:summary="Double-tap overlay to start/stop capture logs"
android:defaultValue="false" />
<SwitchPreferenceCompat
android:key="game_bar_longpress_enable"
android:title="Enable Long Press"
android:summary="Long-press overlay to access GameBar settings"
android:defaultValue="false" />
<ListPreference
android:key="game_bar_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_bar_longpress_entries"
android:entryValues="@array/game_bar_longpress_values"
android:dependency="game_bar_longpress_enable" />
</PreferenceCategory>
<PreferenceCategory
android:title="Capture Logs"
android:dependency="game_bar_enable">
<Preference
android:key="game_bar_capture_start"
android:title="Start Logging"
android:summary="Begin capturing FPS and performance data in real-time" />
<Preference
android:key="game_bar_capture_stop"
android:title="Stop Logging"
android:summary="Stop capturing FPS and performance data in real-time" />
<Preference
android:key="game_bar_capture_export"
android:title="Export GameBar Log Data"
android:summary="Save the captured FPS and performance data as a CSV file" />
</PreferenceCategory>
<PreferenceCategory
android:title="Auto Enable GameBar">
<SwitchPreferenceCompat
android:key="game_bar_auto_enable"
android:title="Auto-Enable GameBar for Selected Apps"
android:summary="If turned on, selected apps will auto-enable GameBar even if the main switch is off"
android:defaultValue="false" />
<Preference
android:key="game_bar_app_selector"
android:title="Select Apps"
android:summary="Add apps that should auto-enable GameBar" />
<Preference
android:key="game_bar_app_remover"
android:title="Remove Selected Apps"
android:summary="Remove apps from the auto-enable list" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -93,4 +93,22 @@
android:title="@string/usb2_fc_title" />
</PreferenceCategory>-->
<PreferenceCategory
android:key="gaming"
android:title="@string/gaming_category_title">
<Preference
android:key="bypass_charging"
android:icon="@drawable/ic_bypass_charging"
android:title="@string/bypass_charging_title"
android:summary="@string/bypass_charging_summary"
android:fragment="org.lineageos.device.DeviceSettings.bypasschrg.BypassChargingFragment" />
<Preference
android:key="game_bar"
android:icon="@drawable/ic_game_bar"
android:title="@string/game_bar_title"
android:summary="@string/game_bar_summary"
android:fragment="org.lineageos.device.DeviceSettings.gamebar.GameBarFragment" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2015 The CyanogenMod Project
* 2017-2020 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.
*/
package org.lineageos.device.DeviceSettings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import androidx.preference.PreferenceManager;
import org.lineageos.device.DeviceSettings.gamebar.GameBar;
import org.lineageos.device.DeviceSettings.gamebar.GameBarMonitorService;
public class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
restoreGameBarOverlayState(context);
}
}
private void restoreGameBarOverlayState(Context context) {
var prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean mainEnabled = prefs.getBoolean("game_bar_enable", false);
boolean autoEnabled = prefs.getBoolean("game_bar_auto_enable", false);
if (mainEnabled) {
// Restore game bar preferences
GameBar.getInstance(context).applyPreferences();
GameBar.getInstance(context).show();
}
if (autoEnabled) {
// Start GameBarMonitorService
Intent monitorIntent = new Intent(context, GameBarMonitorService.class);
context.startService(monitorIntent);
}
}
}

View File

@@ -384,14 +384,14 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
});
}
public static void restoreFastChargeSetting(Context context) {
if (Utils.fileWritable(FILE_FAST_CHARGE)) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean value = sharedPrefs.getBoolean(KEY_USB2_SWITCH,
Utils.getFileValueAsBoolean(FILE_FAST_CHARGE, false));
Utils.writeValue(FILE_FAST_CHARGE, value ? "1" : "0");
}
}
// public static void restoreFastChargeSetting(Context context) {
// if (Utils.fileWritable(FILE_FAST_CHARGE)) {
// SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// boolean value = sharedPrefs.getBoolean(KEY_USB2_SWITCH,
// Utils.getFileValueAsBoolean(FILE_FAST_CHARGE, false));
// Utils.writeValue(FILE_FAST_CHARGE, value ? "1" : "0");
// }
// }
private static int getDefaultResIdForUsage(String usage) {
switch (usage) {

View File

@@ -16,29 +16,51 @@
package org.lineageos.device.DeviceSettings;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceManager;
import android.view.View;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
public class DeviceSettingsActivity extends CollapsingToolbarBaseActivity {
private View bannerFadeOverlay;
private boolean pinned = false; // Set true to pin the fade overlay
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(" ");
// Load your fragment as usual
getSupportFragmentManager().beginTransaction().replace(
com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new DeviceSettings()).commit();
R.id.content_frame,
new DeviceSettings()).commit();
// Inject banner dynamically into CollapsingToolbarLayout
CollapsingToolbarLayout collapsingToolbar =
findViewById(R.id.collapsing_toolbar);
if (collapsingToolbar != null) {
View banner = getLayoutInflater().inflate(R.layout.banner_collapsing_toolbar, collapsingToolbar, false);
// You may want to insert at position 0 to ensure it's on top
collapsingToolbar.addView(banner, 0);
bannerFadeOverlay = banner.findViewById(R.id.bannerFadeOverlay);
// Animate fade overlay on scroll
AppBarLayout appBar = findViewById(R.id.app_bar);
if (appBar != null) {
appBar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if (bannerFadeOverlay == null) return;
int totalScrollRange = appBarLayout.getTotalScrollRange();
float offsetFraction = Math.abs(verticalOffset) / (float) totalScrollRange;
float maxAlpha = 0.8f;
float alpha = pinned ? maxAlpha : maxAlpha * (1 - offsetFraction);
bannerFadeOverlay.setAlpha(alpha);
});
}
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.device.DeviceSettings;
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.device.DeviceSettings.bypasschrg.BypassChargingActivity;
import org.lineageos.device.DeviceSettings.bypasschrg.BypassChargingTile;
import org.lineageos.device.DeviceSettings.gamebar.GameBarSettingsActivity;
import org.lineageos.device.DeviceSettings.gamebar.GameBarTileService;
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(GameBarTileService.class.getName(), GameBarSettingsActivity.class);
TILE_ACTIVITY_MAP.put(BypassChargingTile.class.getName(), BypassChargingActivity.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,33 @@
/*
* Copyright (C) 2025 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.
*/
package org.lineageos.device.DeviceSettings.bypasschrg;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import org.lineageos.device.DeviceSettings.R;
public class BypassChargingActivity extends CollapsingToolbarBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bypass_charging);
setTitle(getString(R.string.bypass_charging_title));
}
}

View File

@@ -0,0 +1,219 @@
/*
* Copyright (C) 2025 The LineageOS Project
* Copyright (C) 2025 AlphaDroid
*
* 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.device.DeviceSettings.bypasschrg;
import static lineageos.health.HealthInterface.MODE_AUTO;
import static lineageos.health.HealthInterface.MODE_LIMIT;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
import androidx.preference.PreferenceManager;
import org.lineageos.device.DeviceSettings.R;
import org.lineageos.device.DeviceSettings.utils.FileUtils;
/**
* This class is implemented to coexist with Lineage Charging Control (CC).
* Bypass Charging will override (disable) CC, while it's enabled.
* CC status will be restored, when Bypass Charging is disabled.
* Any user changes to CC settings, while Bypass Charging is enabled,
* will override Bypass Charging settings.
*/
public class BypassChargingController {
private static final boolean DEBUG = false;
private static final String TAG = "BypassChargingController";
private static final String BYPASS_CHARGING_NODE = "/sys/class/oplus_chg/battery/mmi_charging_enable";
private static final String KEY_BYPASS_CHARGING_ENABLED = "bypass_charging_enabled";
// Bypass modes
private static final String BYPASS_CHARGING_ENABLED = "0";
private static final String BYPASS_CHARGING_DISABLED = "1";
private static final int CC_LIMIT_MIN = 10;
private static final int CC_LIMIT_MAX = 100;
private static final int CC_LIMIT_DEF = 80;
// Charging Control settings
private static final String KEY_CHARGING_CONTROL_ENABLED = "charging_control_enabled";
private static final String KEY_CHARGING_CONTROL_MODE = "charging_control_mode";
private static final String KEY_CHARGING_CONTROL_LIMIT = "charging_control_charging_limit";
private Context mContext;
private ContentResolver mContentResolver;
private static BypassChargingController sInstance;
public static synchronized BypassChargingController getInstance(Context context) {
if (sInstance == null) {
sInstance = new BypassChargingController(context);
}
return sInstance;
}
private BypassChargingController(Context context) {
mContext = context.getApplicationContext();
mContentResolver = mContext.getContentResolver();
}
private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
switch(uri.getLastPathSegment()) {
case KEY_CHARGING_CONTROL_ENABLED:
case KEY_CHARGING_CONTROL_MODE:
case KEY_CHARGING_CONTROL_LIMIT:
break;
}
}
};
public boolean isBypassChargingSupported() {
return isNodeAccessible(BYPASS_CHARGING_NODE);
}
public boolean isBypassChargingEnabled() {
try {
String value = FileUtils.readOneLine(BYPASS_CHARGING_NODE);
return value != null && BYPASS_CHARGING_ENABLED.equals(value);
} catch (Exception e) {
Log.e(TAG, "Failed to read bypass sysnode", e);
return false;
}
}
private boolean isNodeAccessible(String node) {
try {
String value = FileUtils.readOneLine(node);
return true;
} catch (Exception e) {
Log.e(TAG, "Node " + node + " not accessible", e);
return false;
}
}
private boolean writeToNode(String value) {
try {
FileUtils.writeLine(BYPASS_CHARGING_NODE, value);
} catch (Exception e) {
Log.e(TAG, "Failed to write bypass sysnode", e);
return false;
}
return true;
}
public void setBypassCharging(boolean enable) {
if (enable) {
enableBypassCharging();
}
else {
disableBypassCharging();
}
}
private void enableBypassCharging() {
setChargingControlEnabled(true);
setChargingControlMode(MODE_LIMIT);
setChargingControlLimit(CC_LIMIT_MIN);
writeToNode(BYPASS_CHARGING_ENABLED);
}
public void disableBypassCharging() {
writeToNode(BYPASS_CHARGING_DISABLED);
setChargingControlLimit(CC_LIMIT_DEF);
// setChargingControlMode(MODE_AUTO);
setChargingControlEnabled(false);
}
private void saveBypassChargingEnabled(boolean enabled) {
PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
.putBoolean(KEY_BYPASS_CHARGING_ENABLED, enabled)
.commit();
}
private boolean isSavedBypassChargingEnabled() {
return PreferenceManager.getDefaultSharedPreferences(mContext)
.getBoolean(KEY_BYPASS_CHARGING_ENABLED, false);
}
private void backupChargingControlSettings() {
PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
.putInt(KEY_CHARGING_CONTROL_MODE, getChargingControlMode())
.putInt(KEY_CHARGING_CONTROL_LIMIT, getChargingControlLimit())
.putBoolean(KEY_CHARGING_CONTROL_ENABLED, isChargingControlEnabled())
.commit();
}
private void restoreChargingControlSettings() {
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(mContext);
setChargingControlMode(sharedPreferences.getInt(
KEY_CHARGING_CONTROL_LIMIT, CC_LIMIT_DEF));
setChargingControlMode(sharedPreferences.getInt(
KEY_CHARGING_CONTROL_MODE, MODE_AUTO));
setChargingControlEnabled(sharedPreferences.getBoolean(
KEY_CHARGING_CONTROL_ENABLED, false));
}
private boolean isChargingControlEnabled() {
return Settings.System.getInt(mContentResolver,
KEY_CHARGING_CONTROL_ENABLED, 0) != 0;
}
private void setChargingControlEnabled(boolean enabled) {
Settings.System.putInt(mContentResolver,
KEY_CHARGING_CONTROL_ENABLED, enabled ? 1 : 0);
}
private int getChargingControlMode() {
return Settings.System.getInt(mContentResolver,
KEY_CHARGING_CONTROL_MODE, MODE_AUTO);
}
private void setChargingControlMode(int mode) {
Settings.System.putInt(mContentResolver,
KEY_CHARGING_CONTROL_MODE, mode);
}
private int getChargingControlLimit() {
return Settings.System.getInt(mContentResolver,
KEY_CHARGING_CONTROL_LIMIT, CC_LIMIT_DEF);
}
private void setChargingControlLimit(int limit) {
if (limit < CC_LIMIT_MIN || limit > CC_LIMIT_MAX) {
return;
}
Settings.System.putInt(mContentResolver,
KEY_CHARGING_CONTROL_LIMIT, limit);
}
private void showToast(int resId) {
Toast.makeText(mContext, mContext.getString(resId),
Toast.LENGTH_LONG).show();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2025 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.
*/
package org.lineageos.device.DeviceSettings.bypasschrg;
import android.os.Bundle;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.TwoStatePreference;
import org.lineageos.device.DeviceSettings.R;
public class BypassChargingFragment extends PreferenceFragmentCompat {
private static final String KEY_BYPASS_CHARGING = "bypass_charging";
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.bypass_charging_settings, rootKey);
BypassChargingController bypassController =
BypassChargingController.getInstance(getContext());
boolean bypassSupported = bypassController.isBypassChargingSupported();
TwoStatePreference bypassPreference = findPreference(KEY_BYPASS_CHARGING);
bypassPreference.setEnabled(bypassSupported);
if (bypassSupported) {
bypassPreference.setChecked(bypassController.isBypassChargingEnabled());
bypassPreference.setOnPreferenceChangeListener((pref, newValue) -> {
bypassController.setBypassCharging((boolean) newValue);
return true;
});
} else {
bypassPreference.setSummary(R.string.bypass_charging_unavailable);
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.device.DeviceSettings.bypasschrg;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import org.lineageos.device.DeviceSettings.R;
public class BypassChargingTile extends TileService {
private BypassChargingController mBypassController;
private boolean mEnabled;
@Override
public void onCreate() {
super.onCreate();
mBypassController = BypassChargingController.getInstance(this);
}
@Override
public void onStartListening() {
mEnabled = mBypassController.isBypassChargingEnabled();
updateTileState();
}
@Override
public void onClick() {
if (mEnabled == mBypassController.isBypassChargingEnabled()) {
mEnabled = !mEnabled;
updateTileState();
mBypassController.setBypassCharging(mEnabled);
}
}
private void updateTileState() {
Tile tile = getQsTile();
if (tile == null) return;
tile.setState(mEnabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
tile.setLabel(getString(R.string.bypass_charging_title));
tile.setContentDescription(getString(R.string.bypass_charging_summary));
tile.updateTile();
}
}

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.device.DeviceSettings.gamebar;
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,774 @@
/*
* 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.device.DeviceSettings.gamebar;
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.device.DeviceSettings.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 GameBar {
private static GameBar sInstance;
public static synchronized GameBar getInstance(Context context) {
if (sInstance == null) {
sInstance = new GameBar(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_bar_x";
private static final String PREF_KEY_Y = "game_bar_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 mOverlayFormat = "full";
private String mPosition = "top_left";
private String mSplitMode = "stacked";
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 GameBar(Context context) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mHandler = new Handler(Looper.getMainLooper());
mBgDrawable = new GradientDrawable();
applyBackgroundStyle();
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";
PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
.putString("game_bar_format", mOverlayFormat)
.apply();
Toast.makeText(mContext, "Overlay Format: " + mOverlayFormat, Toast.LENGTH_SHORT).show();
updateStats();
return true;
}
return super.onSingleTapConfirmed(e);
}
});
}
public void applyPreferences() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mShowFps = prefs.getBoolean("game_bar_fps_enable", false);
mShowBatteryTemp = prefs.getBoolean("game_bar_temp_enable", false);
mShowCpuUsage = prefs.getBoolean("game_bar_cpu_usage_enable", false);
mShowCpuClock = prefs.getBoolean("game_bar_cpu_clock_enable", false);
mShowCpuTemp = prefs.getBoolean("game_bar_cpu_temp_enable", false);
mShowRam = prefs.getBoolean("game_bar_ram_enable", false);
mShowGpuUsage = prefs.getBoolean("game_bar_gpu_usage_enable", false);
mShowGpuClock = prefs.getBoolean("game_bar_gpu_clock_enable", false);
mShowGpuTemp = prefs.getBoolean("game_bar_gpu_temp_enable", false);
mDoubleTapCaptureEnabled = prefs.getBoolean("game_bar_doubletap_capture", false);
mSingleTapToggleEnabled = prefs.getBoolean("game_bar_single_tap_toggle", false);
updateSplitMode(prefs.getString("game_bar_split_mode", "stacked"));
updateTextSize(prefs.getInt("game_bar_text_size", 16));
updateBackgroundAlpha(prefs.getInt("game_bar_background_alpha", 128));
updateCornerRadius(prefs.getInt("game_bar_corner_radius", 16));
updatePadding(prefs.getInt("game_bar_padding", 12));
updateTitleColor(prefs.getString("game_bar_title_color", "#FFFFFF"));
updateValueColor(prefs.getString("game_bar_value_color", "#4CAF50"));
updateOverlayFormat(prefs.getString("game_bar_format", "full"));
updateUpdateInterval(prefs.getString("game_bar_update_interval", "1000"));
updatePosition(prefs.getString("game_bar_position", "top_left"));
int spacing = prefs.getInt("game_bar_item_spacing", 8);
updateItemSpacing(spacing);
mLongPressEnabled = prefs.getBoolean("game_bar_longpress_enable", false);
String lpTimeoutStr = prefs.getString("game_bar_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();
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();
// Start the FPS meter if using the new API method.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
GameBarFpsMeter.getInstance(mContext).start();
}
}
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;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
GameBarFpsMeter.getInstance(mContext).stop();
}
}
private void updateStats() {
if (!mIsShowing || mRootLayout == null) return;
mRootLayout.removeAllViews();
List<View> statViews = new ArrayList<>();
// 1) FPS
float fpsVal = GameBarFpsMeter.getInstance(mContext).getFps();
String fpsStr = fpsVal >= 0 ? String.format(Locale.getDefault(), "%.0f", 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 = GameBarCpuInfo.getCpuUsage();
String display = "N/A".equals(cpuUsageStr) ? "N/A" : cpuUsageStr + "%";
statViews.add(createStatLine("CPU", display));
}
// 4) CPU freq
if (mShowCpuClock) {
List<String> freqs = GameBarCpuInfo.getCpuFrequencies();
if (!freqs.isEmpty()) {
statViews.add(buildCpuFreqView(freqs));
}
}
// 5) CPU temp
String cpuTempStr = "N/A";
if (mShowCpuTemp) {
cpuTempStr = GameBarCpuInfo.getCpuTemp();
statViews.add(createStatLine("CPU Temp", "N/A".equals(cpuTempStr) ? "N/A" : cpuTempStr + "°C"));
}
// 6) RAM usage
String ramStr = "N/A";
if (mShowRam) {
ramStr = GameBarMemInfo.getRamUsage();
statViews.add(createStatLine("RAM", "N/A".equals(ramStr) ? "N/A" : ramStr + " MB"));
}
// 7) GPU usage
String gpuUsageStr = "N/A";
if (mShowGpuUsage) {
gpuUsageStr = GameBarGpuInfo.getGpuUsage();
statViews.add(createStatLine("GPU", "N/A".equals(gpuUsageStr) ? "N/A" : gpuUsageStr + "%"));
}
// 8) GPU clock
String gpuClockStr = "N/A";
if (mShowGpuClock) {
gpuClockStr = GameBarGpuInfo.getGpuClock();
statViews.add(createStatLine("GPU Freq", "N/A".equals(gpuClockStr) ? "N/A" : gpuClockStr + "MHz"));
}
// 9) GPU temp
String gpuTempStr = "N/A";
if (mShowGpuTemp) {
gpuTempStr = GameBarGpuInfo.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,
fpsStr,
batteryTempStr,
cpuUsageStr,
cpuTempStr,
gpuUsageStr,
gpuClockStr,
gpuTempStr
);
}
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;
}
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, GameBarSettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} catch (Exception e) {
// Exception ignored
}
}
private static int dpToPx(Context context, int dp) {
float scale = context.getResources().getDisplayMetrics().density;
return Math.round(dp * scale);
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import org.lineageos.device.DeviceSettings.R;
public class GameBarAppRemoverActivity extends CollapsingToolbarBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_bar_app_selector);
setTitle("Remove Auto-Enable Apps");
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frame, new GameBarAppRemoverFragment())
.commit();
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.lineageos.device.DeviceSettings.R;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class GameBarAppRemoverFragment extends Fragment {
private RecyclerView recyclerView;
private GameBarAutoAppsAdapter adapter;
private PackageManager packageManager;
private List<ApplicationInfo> autoAppsList;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.game_bar_app_selector, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
recyclerView = view.findViewById(R.id.app_list);
packageManager = getContext().getPackageManager();
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
loadAutoApps();
}
private void loadAutoApps() {
Set<String> autoAppsSet = getSavedAutoApps();
autoAppsList = new ArrayList<>();
for (String pkg : autoAppsSet) {
try {
ApplicationInfo info = packageManager.getApplicationInfo(pkg, 0);
autoAppsList.add(info);
} catch (PackageManager.NameNotFoundException e) {
}
}
adapter = new GameBarAutoAppsAdapter(packageManager, autoAppsList, new GameBarAutoAppsAdapter.OnAppRemoveListener() {
@Override
public void onAppRemove(ApplicationInfo appInfo) {
removeAppFromAutoList(appInfo.packageName);
Toast.makeText(getContext(), appInfo.loadLabel(packageManager) + " removed.", Toast.LENGTH_SHORT).show();
autoAppsList.remove(appInfo);
adapter.notifyDataSetChanged();
}
});
recyclerView.setAdapter(adapter);
}
private Set<String> getSavedAutoApps() {
return PreferenceManager.getDefaultSharedPreferences(getContext())
.getStringSet(GameBarAppSelectorFragment.PREF_AUTO_APPS, new HashSet<>());
}
private void removeAppFromAutoList(String packageName) {
Set<String> autoApps = new HashSet<>(getSavedAutoApps());
autoApps.remove(packageName);
PreferenceManager.getDefaultSharedPreferences(getContext())
.edit().putStringSet(GameBarAppSelectorFragment.PREF_AUTO_APPS, autoApps).apply();
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import org.lineageos.device.DeviceSettings.R;
public class GameBarAppSelectorActivity extends CollapsingToolbarBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_bar_app_selector);
setTitle("Select Apps for GameBar");
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frame, new GameBarAppSelectorFragment())
.commit();
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.lineageos.device.DeviceSettings.R;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class GameBarAppSelectorFragment extends Fragment {
public static final String PREF_AUTO_APPS = "game_bar_auto_apps";
private RecyclerView recyclerView;
private GameBarAppsAdapter adapter;
private PackageManager packageManager;
private List<ApplicationInfo> allApps;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.game_bar_app_selector, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
recyclerView = view.findViewById(R.id.app_list);
packageManager = getContext().getPackageManager();
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
loadApps();
}
private void loadApps() {
allApps = new ArrayList<>();
List<ApplicationInfo> installedApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
Set<String> autoApps = getSavedAutoApps();
for (ApplicationInfo appInfo : installedApps) {
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 &&
!appInfo.packageName.equals(getContext().getPackageName()) &&
!autoApps.contains(appInfo.packageName)) {
allApps.add(appInfo);
}
}
adapter = new GameBarAppsAdapter(packageManager, allApps, new GameBarAppsAdapter.OnAppClickListener() {
@Override
public void onAppClick(ApplicationInfo appInfo) {
addAppToAutoList(appInfo.packageName);
Toast.makeText(getContext(), appInfo.loadLabel(packageManager) + " added.", Toast.LENGTH_SHORT).show();
allApps.remove(appInfo);
adapter.notifyDataSetChanged();
}
});
recyclerView.setAdapter(adapter);
}
private Set<String> getSavedAutoApps() {
return PreferenceManager.getDefaultSharedPreferences(getContext())
.getStringSet(PREF_AUTO_APPS, new HashSet<>());
}
private void addAppToAutoList(String packageName) {
Set<String> autoApps = new HashSet<>(getSavedAutoApps());
autoApps.add(packageName);
PreferenceManager.getDefaultSharedPreferences(getContext())
.edit().putStringSet(PREF_AUTO_APPS, autoApps).apply();
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.lineageos.device.DeviceSettings.R;
import java.util.List;
public class GameBarAppsAdapter extends RecyclerView.Adapter<GameBarAppsAdapter.ViewHolder> {
public interface OnAppClickListener {
void onAppClick(ApplicationInfo appInfo);
}
private PackageManager packageManager;
private List<ApplicationInfo> apps;
private OnAppClickListener listener;
public GameBarAppsAdapter(PackageManager packageManager, List<ApplicationInfo> apps, OnAppClickListener listener) {
this.packageManager = packageManager;
this.apps = apps;
this.listener = listener;
}
@NonNull
@Override
public GameBarAppsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.game_bar_app_selector_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull GameBarAppsAdapter.ViewHolder holder, int position) {
final ApplicationInfo appInfo = apps.get(position);
holder.appName.setText(appInfo.loadLabel(packageManager));
holder.appPackage.setText(appInfo.packageName);
holder.appIcon.setImageDrawable(appInfo.loadIcon(packageManager));
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onAppClick(appInfo);
}
});
}
@Override
public int getItemCount() {
return apps.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView appName;
TextView appPackage;
ImageView appIcon;
public ViewHolder(@NonNull View itemView) {
super(itemView);
appName = itemView.findViewById(R.id.app_name);
appPackage = itemView.findViewById(R.id.app_package);
appIcon = itemView.findViewById(R.id.app_icon);
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.lineageos.device.DeviceSettings.R;
import java.util.List;
public class GameBarAutoAppsAdapter extends RecyclerView.Adapter<GameBarAutoAppsAdapter.ViewHolder> {
public interface OnAppRemoveListener {
void onAppRemove(ApplicationInfo appInfo);
}
private PackageManager packageManager;
private List<ApplicationInfo> apps;
private OnAppRemoveListener listener;
public GameBarAutoAppsAdapter(PackageManager packageManager, List<ApplicationInfo> apps, OnAppRemoveListener listener) {
this.packageManager = packageManager;
this.apps = apps;
this.listener = listener;
}
@NonNull
@Override
public GameBarAutoAppsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.game_bar_app_selector_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull GameBarAutoAppsAdapter.ViewHolder holder, int position) {
final ApplicationInfo appInfo = apps.get(position);
holder.appName.setText(appInfo.loadLabel(packageManager));
holder.appPackage.setText(appInfo.packageName);
holder.appIcon.setImageDrawable(appInfo.loadIcon(packageManager));
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onAppRemove(appInfo);
}
});
}
@Override
public int getItemCount() {
return apps.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView appName;
TextView appPackage;
ImageView appIcon;
public ViewHolder(@NonNull View itemView) {
super(itemView);
appName = itemView.findViewById(R.id.app_name);
appPackage = itemView.findViewById(R.id.app_package);
appIcon = itemView.findViewById(R.id.app_icon);
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import androidx.preference.PreferenceManager;
public class GameBarBootReceiver 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 mainEnabled = prefs.getBoolean("game_bar_enable", false);
boolean autoEnabled = prefs.getBoolean("game_bar_auto_enable", false);
if (mainEnabled) {
GameBar.getInstance(context).applyPreferences();
GameBar.getInstance(context).show();
}
if (autoEnabled) {
Intent monitorIntent = new Intent(context, GameBarMonitorService.class);
context.startService(monitorIntent);
}
}
}

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.device.DeviceSettings.gamebar;
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 GameBarCpuInfo {
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(GameBarCpuInfo::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,187 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.view.WindowManager;
import android.window.TaskFpsCallback;
import androidx.preference.PreferenceManager;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class GameBarFpsMeter {
private static final float TOLERANCE = 0.1f;
private static final long STALENESS_THRESHOLD_MS = 2000;
private static final long TASK_CHECK_INTERVAL_MS = 1000;
private static GameBarFpsMeter sInstance;
private final Context mContext;
private final WindowManager mWindowManager;
private final SharedPreferences mPrefs;
private float mCurrentFps = 0f;
private TaskFpsCallback mTaskFpsCallback;
private boolean mCallbackRegistered = false;
private int mCurrentTaskId = -1;
private long mLastFpsUpdateTime = System.currentTimeMillis();
private final android.os.Handler mHandler = new android.os.Handler();
public static synchronized GameBarFpsMeter getInstance(Context context) {
if (sInstance == null) {
sInstance = new GameBarFpsMeter(context.getApplicationContext());
}
return sInstance;
}
private GameBarFpsMeter(Context context) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mTaskFpsCallback = new TaskFpsCallback() {
@Override
public void onFpsReported(float fps) {
if (fps > 0) {
mCurrentFps = fps;
mLastFpsUpdateTime = System.currentTimeMillis();
}
}
};
}
}
public void start() {
String method = mPrefs.getString("game_bar_fps_method", "new");
if (!"new".equals(method)) return;
stop();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
int taskId = getFocusedTaskId();
if (taskId <= 0) {
return;
}
mCurrentTaskId = taskId;
try {
mWindowManager.registerTaskFpsCallback(mCurrentTaskId, Runnable::run, mTaskFpsCallback);
mCallbackRegistered = true;
} catch (Exception e) {
}
mLastFpsUpdateTime = System.currentTimeMillis();
mHandler.postDelayed(mTaskCheckRunnable, TASK_CHECK_INTERVAL_MS);
}
}
public void stop() {
String method = mPrefs.getString("game_bar_fps_method", "new");
if ("new".equals(method) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (mCallbackRegistered) {
try {
mWindowManager.unregisterTaskFpsCallback(mTaskFpsCallback);
} catch (Exception e) {
}
mCallbackRegistered = false;
}
mHandler.removeCallbacks(mTaskCheckRunnable);
}
}
public float getFps() {
String method = mPrefs.getString("game_bar_fps_method", "new");
if ("legacy".equals(method)) {
return readLegacyFps();
} else {
return mCurrentFps;
}
}
private float readLegacyFps() {
try (BufferedReader br = new BufferedReader(new FileReader("/sys/class/drm/sde-crtc-0/measured_fps"))) {
String line = br.readLine();
if (line != null && line.startsWith("fps:")) {
String[] parts = line.split("\\s+");
if (parts.length >= 2) {
return Float.parseFloat(parts[1].trim());
}
}
} catch (IOException | NumberFormatException e) {
}
return -1f;
}
private final Runnable mTaskCheckRunnable = new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
int newTaskId = getFocusedTaskId();
if (newTaskId > 0 && newTaskId != mCurrentTaskId) {
reinitCallback();
} else {
long now = System.currentTimeMillis();
if (now - mLastFpsUpdateTime > STALENESS_THRESHOLD_MS) {
reinitCallback();
}
}
mHandler.postDelayed(this, TASK_CHECK_INTERVAL_MS);
}
}
};
private int getFocusedTaskId() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
return -1;
}
try {
Class<?> atmClass = Class.forName("android.app.ActivityTaskManager");
Method getServiceMethod = atmClass.getDeclaredMethod("getService");
Object atmService = getServiceMethod.invoke(null);
Method getFocusedRootTaskInfoMethod = atmService.getClass().getMethod("getFocusedRootTaskInfo");
Object taskInfo = getFocusedRootTaskInfoMethod.invoke(atmService);
if (taskInfo != null) {
try {
Field taskIdField = taskInfo.getClass().getField("taskId");
return taskIdField.getInt(taskInfo);
} catch (NoSuchFieldException nsfe) {
try {
Field taskIdField = taskInfo.getClass().getField("mTaskId");
return taskIdField.getInt(taskInfo);
} catch (NoSuchFieldException nsfe2) {
}
}
}
} catch (Exception e) {
}
return -1;
}
private void reinitCallback() {
stop();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
start();
}
}, 500);
}
}

View File

@@ -0,0 +1,394 @@
/*
* 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.device.DeviceSettings.gamebar;
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 androidx.preference.SwitchPreferenceCompat;
import com.android.settingslib.widget.MainSwitchPreference;
import org.lineageos.device.DeviceSettings.R;
public class GameBarFragment extends PreferenceFragmentCompat {
private GameBar mGameBar;
private MainSwitchPreference mMasterSwitch;
private SwitchPreferenceCompat mAutoEnableSwitch;
private SwitchPreferenceCompat mFpsSwitch;
private SwitchPreferenceCompat mBatteryTempSwitch;
private SwitchPreferenceCompat mCpuUsageSwitch;
private SwitchPreferenceCompat mCpuClockSwitch;
private SwitchPreferenceCompat mCpuTempSwitch;
private SwitchPreferenceCompat mRamSwitch;
private SwitchPreferenceCompat mGpuUsageSwitch;
private SwitchPreferenceCompat mGpuClockSwitch;
private SwitchPreferenceCompat mGpuTempSwitch;
private Preference mCaptureStartPref;
private Preference mCaptureStopPref;
private Preference mCaptureExportPref;
private SwitchPreferenceCompat mDoubleTapCapturePref;
private SwitchPreferenceCompat mSingleTapTogglePref;
private SwitchPreferenceCompat 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_bar_preferences, rootKey);
mGameBar = GameBar.getInstance(getContext());
// Initialize all preferences.
mMasterSwitch = findPreference("game_bar_enable");
mAutoEnableSwitch = findPreference("game_bar_auto_enable");
mFpsSwitch = findPreference("game_bar_fps_enable");
mBatteryTempSwitch = findPreference("game_bar_temp_enable");
mCpuUsageSwitch = findPreference("game_bar_cpu_usage_enable");
mCpuClockSwitch = findPreference("game_bar_cpu_clock_enable");
mCpuTempSwitch = findPreference("game_bar_cpu_temp_enable");
mRamSwitch = findPreference("game_bar_ram_enable");
mGpuUsageSwitch = findPreference("game_bar_gpu_usage_enable");
mGpuClockSwitch = findPreference("game_bar_gpu_clock_enable");
mGpuTempSwitch = findPreference("game_bar_gpu_temp_enable");
mCaptureStartPref = findPreference("game_bar_capture_start");
mCaptureStopPref = findPreference("game_bar_capture_stop");
mCaptureExportPref = findPreference("game_bar_capture_export");
mDoubleTapCapturePref = findPreference("game_bar_doubletap_capture");
mSingleTapTogglePref = findPreference("game_bar_single_tap_toggle");
mLongPressEnablePref = findPreference("game_bar_longpress_enable");
mLongPressTimeoutPref = findPreference("game_bar_longpress_timeout");
mTextSizePref = findPreference("game_bar_text_size");
mBgAlphaPref = findPreference("game_bar_background_alpha");
mCornerRadiusPref = findPreference("game_bar_corner_radius");
mPaddingPref = findPreference("game_bar_padding");
mItemSpacingPref = findPreference("game_bar_item_spacing");
mUpdateIntervalPref = findPreference("game_bar_update_interval");
mTextColorPref = findPreference("game_bar_text_color");
mTitleColorPref = findPreference("game_bar_title_color");
mValueColorPref = findPreference("game_bar_value_color");
mPositionPref = findPreference("game_bar_position");
mSplitModePref = findPreference("game_bar_split_mode");
mOverlayFormatPref = findPreference("game_bar_format");
Preference appSelectorPref = findPreference("game_bar_app_selector");
if (appSelectorPref != null) {
appSelectorPref.setOnPreferenceClickListener(pref -> {
Intent intent = new Intent(getContext(), GameBarAppSelectorActivity.class);
startActivity(intent);
return true;
});
}
Preference appRemoverPref = findPreference("game_bar_app_remover");
if (appRemoverPref != null) {
appRemoverPref.setOnPreferenceClickListener(pref -> {
Intent intent = new Intent(getContext(), GameBarAppRemoverActivity.class);
startActivity(intent);
return true;
});
}
if (mMasterSwitch != null) {
mMasterSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
boolean enabled = (boolean) newValue;
if (enabled) {
if (Settings.canDrawOverlays(getContext())) {
mGameBar.applyPreferences();
mGameBar.show();
getContext().startService(new Intent(getContext(), GameBarMonitorService.class));
} else {
Toast.makeText(getContext(), R.string.overlay_permission_required, Toast.LENGTH_SHORT).show();
return false;
}
} else {
mGameBar.hide();
if (mAutoEnableSwitch == null || !mAutoEnableSwitch.isChecked()) {
getContext().stopService(new Intent(getContext(), GameBarMonitorService.class));
}
}
return true;
});
}
if (mAutoEnableSwitch != null) {
mAutoEnableSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
boolean autoEnabled = (boolean) newValue;
if (autoEnabled) {
getContext().startService(new Intent(getContext(), GameBarMonitorService.class));
} else {
if (mMasterSwitch == null || !mMasterSwitch.isChecked()) {
getContext().stopService(new Intent(getContext(), GameBarMonitorService.class));
}
}
return true;
});
}
if (mFpsSwitch != null) {
mFpsSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setShowFps((boolean) newValue);
return true;
});
}
if (mBatteryTempSwitch != null) {
mBatteryTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setShowBatteryTemp((boolean) newValue);
return true;
});
}
if (mCpuUsageSwitch != null) {
mCpuUsageSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setShowCpuUsage((boolean) newValue);
return true;
});
}
if (mCpuClockSwitch != null) {
mCpuClockSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setShowCpuClock((boolean) newValue);
return true;
});
}
if (mCpuTempSwitch != null) {
mCpuTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setShowCpuTemp((boolean) newValue);
return true;
});
}
if (mRamSwitch != null) {
mRamSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setShowRam((boolean) newValue);
return true;
});
}
if (mGpuUsageSwitch != null) {
mGpuUsageSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setShowGpuUsage((boolean) newValue);
return true;
});
}
if (mGpuClockSwitch != null) {
mGpuClockSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setShowGpuClock((boolean) newValue);
return true;
});
}
if (mGpuTempSwitch != null) {
mGpuTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.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) -> {
mGameBar.setDoubleTapCaptureEnabled((boolean) newValue);
return true;
});
}
if (mSingleTapTogglePref != null) {
mSingleTapTogglePref.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setSingleTapToggleEnabled((boolean) newValue);
return true;
});
}
if (mLongPressEnablePref != null) {
mLongPressEnablePref.setOnPreferenceChangeListener((pref, newValue) -> {
mGameBar.setLongPressEnabled((boolean) newValue);
return true;
});
}
if (mLongPressTimeoutPref != null) {
mLongPressTimeoutPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
long ms = Long.parseLong((String) newValue);
mGameBar.setLongPressThresholdMs(ms);
}
return true;
});
}
if (mTextSizePref != null) {
mTextSizePref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mGameBar.updateTextSize((Integer) newValue);
}
return true;
});
}
if (mBgAlphaPref != null) {
mBgAlphaPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mGameBar.updateBackgroundAlpha((Integer) newValue);
}
return true;
});
}
if (mCornerRadiusPref != null) {
mCornerRadiusPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mGameBar.updateCornerRadius((Integer) newValue);
}
return true;
});
}
if (mPaddingPref != null) {
mPaddingPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mGameBar.updatePadding((Integer) newValue);
}
return true;
});
}
if (mItemSpacingPref != null) {
mItemSpacingPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Integer) {
mGameBar.updateItemSpacing((Integer) newValue);
}
return true;
});
}
if (mUpdateIntervalPref != null) {
mUpdateIntervalPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mGameBar.updateUpdateInterval((String) newValue);
}
return true;
});
}
if (mTextColorPref != null) {
mTextColorPref.setOnPreferenceChangeListener((pref, newValue) -> true);
}
if (mTitleColorPref != null) {
mTitleColorPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mGameBar.updateTitleColor((String) newValue);
}
return true;
});
}
if (mValueColorPref != null) {
mValueColorPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mGameBar.updateValueColor((String) newValue);
}
return true;
});
}
if (mPositionPref != null) {
mPositionPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mGameBar.updatePosition((String) newValue);
}
return true;
});
}
if (mSplitModePref != null) {
mSplitModePref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mGameBar.updateSplitMode((String) newValue);
}
return true;
});
}
if (mOverlayFormatPref != null) {
mOverlayFormatPref.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof String) {
mGameBar.updateOverlayFormat((String) newValue);
}
return true;
});
}
}
@Override
public void onResume() {
super.onResume();
if (!hasUsageStatsPermission(requireContext())) {
requestUsageStatsPermission();
}
Context context = getContext();
if (context != null) {
if ((mMasterSwitch != null && mMasterSwitch.isChecked()) ||
(mAutoEnableSwitch != null && mAutoEnableSwitch.isChecked())) {
context.startService(new Intent(context, GameBarMonitorService.class));
} else {
context.stopService(new Intent(context, GameBarMonitorService.class));
}
}
}
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.device.DeviceSettings.gamebar;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class GameBarGpuInfo {
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.device.DeviceSettings.gamebar;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class GameBarMemInfo {
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,82 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import androidx.preference.PreferenceManager;
import java.util.HashSet;
import java.util.Set;
public class GameBarMonitorService extends Service {
private Handler mHandler;
private Runnable mMonitorRunnable;
private static final long MONITOR_INTERVAL = 2000; // 2 seconds
@Override
public void onCreate() {
super.onCreate();
mHandler = new Handler();
mMonitorRunnable = new Runnable() {
@Override
public void run() {
monitorForegroundApp();
mHandler.postDelayed(this, MONITOR_INTERVAL);
}
};
mHandler.post(mMonitorRunnable);
}
private void monitorForegroundApp() {
var prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean masterEnabled = prefs.getBoolean("game_bar_enable", false);
if (masterEnabled) {
GameBar.getInstance(this).applyPreferences();
GameBar.getInstance(this).show();
return;
}
boolean autoEnabled = prefs.getBoolean("game_bar_auto_enable", false);
if (!autoEnabled) {
GameBar.getInstance(this).hide();
return;
}
String foreground = ForegroundAppDetector.getForegroundPackageName(this);
Set<String> autoApps = prefs.getStringSet(GameBarAppSelectorFragment.PREF_AUTO_APPS, new HashSet<>());
if (autoApps.contains(foreground)) {
GameBar.getInstance(this).applyPreferences();
GameBar.getInstance(this).show();
} else {
GameBar.getInstance(this).hide();
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mMonitorRunnable);
}
}

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.device.DeviceSettings.gamebar;
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.device.DeviceSettings.R;
public class GameBarSettingsActivity 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_bar);
setTitle(getString(R.string.game_bar_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,72 @@
/*
* 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.device.DeviceSettings.gamebar;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import androidx.preference.PreferenceManager;
import org.lineageos.device.DeviceSettings.R;
public class GameBarTileService extends TileService {
private GameBar mGameBar;
@Override
public void onCreate() {
super.onCreate();
mGameBar = GameBar.getInstance(this);
}
@Override
public void onStartListening() {
boolean enabled = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("game_bar_enable", false);
updateTileState(enabled);
}
@Override
public void onClick() {
boolean currentlyEnabled = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("game_bar_enable", false);
boolean newState = !currentlyEnabled;
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putBoolean("game_bar_enable", newState)
.commit();
updateTileState(newState);
if (newState) {
mGameBar.applyPreferences();
mGameBar.show();
} else {
mGameBar.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_bar_tile_label));
tile.setContentDescription(getString(R.string.game_bar_tile_description));
tile.updateTile();
}
}

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.device.DeviceSettings.gamebar;
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,176 @@
/*
* Copyright (C) 2016 The CyanogenMod 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.
*/
package org.lineageos.device.DeviceSettings.utils;
import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public final class FileUtils {
private static final String TAG = "FileUtils";
private FileUtils() {
// This class is not supposed to be instantiated
}
/**
* Reads the first line of text from the given file.
* Reference {@link BufferedReader#readLine()} for clarification on what a line is
*
* @return the read line contents, or null on failure
*/
public static String readOneLine(String fileName) {
String line = null;
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName), 512);
line = reader.readLine();
} catch (FileNotFoundException e) {
Log.w(TAG, "No such file " + fileName + " for reading", e);
} catch (IOException e) {
Log.e(TAG, "Could not read from file " + fileName, e);
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// Ignored, not much we can do anyway
}
}
return line;
}
/**
* Writes the given value into the given file
*
* @return true on success, false on failure
*/
public static boolean writeLine(String fileName, String value) {
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(fileName));
writer.write(value);
} catch (FileNotFoundException e) {
Log.w(TAG, "No such file " + fileName + " for writing", e);
return false;
} catch (IOException e) {
Log.e(TAG, "Could not write to file " + fileName, e);
return false;
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
// Ignored, not much we can do anyway
}
}
return true;
}
/**
* Checks whether the given file exists
*
* @return true if exists, false if not
*/
public static boolean fileExists(String fileName) {
final File file = new File(fileName);
return file.exists();
}
/**
* Checks whether the given file is readable
*
* @return true if readable, false if not
*/
public static boolean isFileReadable(String fileName) {
final File file = new File(fileName);
return file.exists() && file.canRead();
}
/**
* Checks whether the given file is writable
*
* @return true if writable, false if not
*/
public static boolean isFileWritable(String fileName) {
final File file = new File(fileName);
return file.exists() && file.canWrite();
}
/**
* Deletes an existing file
*
* @return true if the delete was successful, false if not
*/
public static boolean delete(String fileName) {
final File file = new File(fileName);
boolean ok = false;
try {
ok = file.delete();
} catch (SecurityException e) {
Log.w(TAG, "SecurityException trying to delete " + fileName, e);
}
return ok;
}
/**
* Renames an existing file
*
* @return true if the rename was successful, false if not
*/
public static boolean rename(String srcPath, String dstPath) {
final File srcFile = new File(srcPath);
final File dstFile = new File(dstPath);
boolean ok = false;
try {
ok = srcFile.renameTo(dstFile);
} catch (SecurityException e) {
Log.w(TAG, "SecurityException trying to rename " + srcPath + " to " + dstPath, e);
} catch (NullPointerException e) {
Log.e(TAG, "NullPointerException trying to rename " + srcPath + " to " + dstPath, e);
}
return ok;
}
public static boolean getFileValueAsBoolean(String filename, boolean defValue) {
String fileValue = readOneLine(filename);
if(fileValue!=null){
return (fileValue.equals("0")?false:true);
}
return defValue;
}
public static String getFileValue(String filename, String defValue) {
String fileValue = readOneLine(filename);
if(fileValue!=null){
return fileValue;
}
return defValue;
}
}

View File

@@ -5,8 +5,5 @@ audio.offload.min.duration.secs=30
audio.offload.video=true
ro.af.client_heap_size_kbyte=7168
# Device name
ro.product.marketname=OnePlus 11 5G
# GMS
ro.opa.device_model_id=ga-oplus-skill-os121-211011