[SQUASH] sapphire: parts: Import XiaomiParts

e261035d91/parts

Change-Id: I442f48780ca020f3e420ee4ccaf8ac07b05457cb
Signed-off-by: Anush02198 <anush.4376@gmail.com>
Signed-off-by: Ramadhani <ramadhanifals@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garnet: parts: Fix cannot find symbol error for UQPR2

Signed-off-by: Ramadhani <ramadhanifals@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garnet: parts: Migrate to CompoundButton.OnCheckedChangeListener

This fixes compilation error for UQPR2.

Signed-off-by: Ramadhani <ramadhanifals@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garnet: parts: Checkout thermal profiles to xiaomi-sm8250 implementation

Signed-off-by: Ramadhani <ramadhanifals@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garnet: parts: Fix thermal profile screen state handling

Signed-off-by: Ramadhani <ramadhanifals@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garnet: Remove order preference for Thermal Profiles
- In Infinity X rom this tab at top spoils the look of Battery section

Signed-off-by: Ramadhani <ramadhanifals@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garnet: parts: Drop doze settings

Signed-off-by: kleidione <kleidione@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garnet: parts: Refactor ClearSpeakerFragment code

 - Removed unused imports
 - Proper resource release by using try-finally block
 - Added null checks for MediaPlayer methods
 - Remove audio parameter as its unused

Change-Id: I7dc2ba7baf6e0193e13583a7454da296254d7333
Signed-off-by: Jyotiraditya Panda <jyotiraditya@aospa.co>
Signed-off-by: Adithya R <gh0strider.2k18.reborn@gmail.com>
Signed-off-by: Coptan99 <omarcoptan9@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garent: parts: Get rid of proguard flags

This is obsolete.

Change-Id: I1b692d3e1a96c49ac6efd54907ea23c4b4494f7e
Signed-off-by: Ramadhani <ramadhanifals@gmail.com>
Signed-off-by: Drenzzz <naufalnazya@gmail.com>

garnet: parts: Supports 90hz refresh rate
Co-Authored-By: Arian <39061882+ArianK16a@users.noreply.github.com>

sapphire: parts: Remove unused HDR override in BootCompletedReceiver for
non-HDR devices

Removed the `overrideHdrTypes` method and its call in
`BootCompletedReceiver`
since the target device does not support HDR10, Dolby Vision, HLG, or
HDR10+.
Also removed unused imports (`DisplayManager`, `Display`,
`HdrCapabilities`,
`IntentFilter`, `IBinder`) to clean up the code. This prevents the
system from
falsely advertising HDR capabilities, avoiding potential app
compatibility issues.
The `ThermalUtils` and `RefreshUtils` service startups remain unchanged.

Signed-off-by: Saroj-Tajpuriya
<80759612+saroj-nokia@users.noreply.github.com>

garnet: parts: Start parts services as early as possible on bootup

BOOT_COMPLETED intent is broadcast only after the device is unlocked and
user data (encrypted storage) is available.
Use LOCKED_BOOT_COMPLETED intent for services that don't require
accessing data.

Change-Id: I800df08f9c61925ac394f3e6d4861ba68a157797
Signed-off-by: kenway214 <kenway214@outlook.com>
Co-Authored-By: Adithya R <gh0strider.2k18.reborn@gmail.com>
Signed-off-by: UnmoveD <unmoved2121@gmail.com>

sapphire: Remove HDR type override from BootCompletedReceiver

sapphire: parts: Add permissions and whitelist for Xiaomi parts

- Added `privapp_whitelist_org.lineageos.settings.xml

- Updated AndroidManifest.xml with required permissions:
  - `android.permission.INTERACT_ACROSS_USERS_FULL` for cross-user
interactions.
  - `android.permission.WRITE_SETTINGS` for modifying system settings.

- Whitelisted `android.permission.WRITE_SECURE_SETTINGS` for the
`org.lineageos.settings`
   package to ensure functionality requiring secure settings
modification.

Signed-off-by: kenway214 <kenway214@outlook.com>
Signed-off-by: UnmoveD <unmoved2121@gmail.com>

sapphire: parts: Introduce Display Satutaion Service and TileUtils

* Adjust Saturation Value Handling for Device Color Mode Compatibility

This update refines the saturation adjustment mechanism. When the seek
bar progress is set, the method executes
the command "service call SurfaceFlinger 1022 f [saturation]" via
Runtime.getRuntime().exec() to apply the saturation changes.
However, it was observed that when the device's color mode is set to
"saturated," a saturation value of 1.0 does not trigger any visual
changes.
To address this, the code now sets a value of 1.001 for a seek bar value
of 100, ensuring the desired effect is consistently applied.

* TileUtils Implementation: Implemented TileUtils to handle the addition
of Quick Settings tiles, specifically for the saturation feature.
This is done with reference to the guidelines provided by Google
https://developer.android.com/develop/ui/views/quicksettings-tiles#prompt-user
this ensuring a seamless integration and user experience when creating
new tiles.

Signed-off-by: Pabloescobar-reborn <pabloescobarreborn77@gmail.com>
Co-Authored-By: kenway214 <kenway214@outlook.com>
Co-Authored-By: AnierinB <anierin@evolution-x.org>
Co-Authored-By: SKULSHADY <anushekprasal@gmail.com>
Co-Authored-By: jhenrique09 <jhenrique09.mcz@hotmail.com>
Signed-off-by: UnmoveD <unmoved2121@gmail.com>

sapphire: parts: Remove unused import for DisplayManager in
BootCompletedReceiver

sapphire: parts: Introduce High Brightness Mode (HBM) and Auto HBM QS
tile

* Before anyone bullies me for this, I know this is very dumb way, but
it works.....
  ( its just a haxx not actual hbm)

* Also introduce TileHandlerActivity to add support for long-press
actions on QS tiles
- AutoHbmTileService → AutoHbmActivity

Signed-off-by: pabloescobar-reborn <pabloescobarreborn77@gmail.com>
Signed-off-by: kenway214 <kenway214@outlook.com>
Co-Authored-By: AnierinB <anierin@evolution-x.org>
Signed-off-by: UnmoveD <unmoved2121@gmail.com>
Signed-off-by: Saroj-Tajpuriya
<80759612+saroj-nokia@users.noreply.github.com>

sapphire: parts: Switch back to thermal profiles
nuked thermal tile service

Signed-off-by: UnmoveD <unmoved2121@gmail.com>

sapphire: parts: Introduce GameBar v4.0

Signed-off-by: kenway214 <kenway214@outlook.com>

peridot: parts: Add FPS meter support with selectable method for GameBar

- Introduced `GameBarFpsMeter` class to support FPS overlay using the
new Android 13+ TaskFpsCallback API.
- Added fallback legacy sysfs method
(`/sys/class/drm/sde-crtc-0/measured_fps`) for older devices.
- Updated GameBar to show FPS using selected method.

Signed-off-by: kenway214 <kenway214@outlook.com>

bump to v4
4

Signed-off-by: UnmoveD <unmoved2121@gmail.com>

sapphire: parts: Fix compilation error in BootCompletedReceiver for
DozeUtils

Signed-off-by: Saroj-Tajpuriya
<80759612+saroj-nokia@users.noreply.github.com>

Update BootCompletedReceiver.java

Signed-off-by: Saroj-Tajpuriya
<80759612+saroj-nokia@users.noreply.github.com>

sapphire: parts: remove duplicate TileHandlerActivity

Signed-off-by: Saroj-Tajpuriya
<80759612+saroj-nokia@users.noreply.github.com>

Co-Authored-By: AspectPower <AspectPower@proton.me>
Co-Authored-By: kenway214 <kenway214@outlook.com>
Co-Authored-By: AnierinB <anierin@evolution-x.org>
Co-Authored-By: SKULSHADY <anushekprasal@gmail.com>
Co-Authored-By: jhenrique09 <jhenrique09.mcz@hotmail.com>
Co-Authored-By: Arian <39061882+ArianK16a@users.noreply.github.com>
Co-Authored-By: Jyotiraditya Panda <jyotiraditya@aospa.co>
Co-Authored-By: Adithya R <gh0strider.2k18.reborn@gmail.com>
Co-Authored-By: Coptan99 <omarcoptan9@gmail.com>
Co-Authored-By: Drenzzz <naufalnazya@gmail.com>
Co-Authored-By: Ramadhani <ramadhanifals@gmail.com>
Co-Authored-By: Anush02198 <anush.4376@gmail.com>
Co-Authored-By: UnmoveD <unmoved2121@gmail.com>
This commit is contained in:
Saroj-Tajpuriya
2025-04-16 04:07:38 +00:00
committed by Liekoo
parent 914dce7be1
commit 384e067254
103 changed files with 7949 additions and 0 deletions

View File

@@ -452,6 +452,10 @@ endif
DEVICE_PACKAGE_OVERLAYS += \
$(LOCAL_PATH)/overlay-lineage
# Device-specific settings
PRODUCT_PACKAGES += \
XiaomiParts
# Partitions
PRODUCT_USE_DYNAMIC_PARTITIONS := true

32
parts/Android.bp Normal file
View File

@@ -0,0 +1,32 @@
//
// Copyright (C) 2017-2020 The LineageOS Project
//
// SPDX-License-Identifier: Apache-2.0
//
android_app {
name: "XiaomiParts",
srcs: ["src/**/*.java"],
certificate: "platform",
platform_apis: true,
system_ext_specific: true,
privileged: true,
static_libs: [
"org.lineageos.settings.resources",
],
required: [
"privapp_whitelist_org.lineageos.settings.xml",
],
}
prebuilt_etc {
name: "privapp_whitelist_org.lineageos.settings.xml",
src: "permissions/privapp_whitelist_org.lineageos.settings.xml",
sub_dir: "permissions",
system_ext_specific: true,
}

268
parts/AndroidManifest.xml Normal file
View File

@@ -0,0 +1,268 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015-2016 The CyanogenMod Project
2017-2018 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.lineageos.settings"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="android.uid.system">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
<uses-sdk
android:minSdkVersion="24"
android:targetSdkVersion="30"/>
<application
android:label="@string/device_settings_app_name"
android:persistent="true"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
android:theme="@style/Theme.SubSettingsBase">
<receiver
android:name=".BootCompletedReceiver"
android:exported="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>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:replace="android:authorities"/>
<receiver
android:name=".Startup"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".thermal.ThermalActivity"
android:label="@string/thermal_title">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.battery" />
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/thermal_summary" />
</activity>
<service
android:name=".thermal.ThermalService"
android:permission="ThermalService">
</service>
<activity
android:name=".speaker.ClearSpeakerActivity"
android:label="@string/clear_speaker_title">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.sound" />
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/clear_speaker_summary" />
</activity>
<activity
android:name=".refreshrate.RefreshActivity"
android:label="@string/refresh_title"
android:exported="false">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.display" />
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/refresh_summary" />
</activity>
<service
android:name=".refreshrate.RefreshService"
android:exported="true"
android:permission="RefreshService">
</service>
<!-- Display Saturation activity -->
<activity
android:name=".saturation.SaturationActivity"
android:label="@string/saturation_title"
android:exported="true">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.display" />
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/saturation_summary" />
</activity>
<!-- Display Saturation tile service -->
<service
android:name=".saturation.SaturationTileService"
android:label="@string/saturation_title"
android:icon="@drawable/ic_saturation_tile"
android:exported="true"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</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>
<!-- Auto HBM activity -->
<activity
android:name=".autohbm.AutoHbmActivity"
android:label="@string/auto_hbm_title"
android:exported="true">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.display" />
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/auto_hbm_summary" />
</activity>
<!-- Auto HBM service -->
<service
android:name=".autohbm.AutoHbmService"
android:exported="false" />
<!-- Auto HBM tile service -->
<service
android:name=".autohbm.AutoHbmTileService"
android:label="@string/auto_hbm_title"
android:icon="@drawable/ic_auto_hbm_tile"
android:exported="true"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<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>
<!--HBM tile service -->
<service
android:name=".autohbm.HbmTileService"
android:label="@string/tile_hbm"
android:icon="@drawable/ic_hbm_tile"
android:exported="true"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<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 -->
<activity
android:name=".gamebar.GameBarSettingsActivity"
android:label="@string/game_bar_title"
android:theme="@style/Theme.SubSettingsBase"
android:exported="true">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.apps" />
<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_gamebar"
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" />
<!-- GameBar BootReceiver -->
<receiver
android:name=".gamebar.GameBarBootReceiver"
android:exported="true"
android:enabled="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2023 Paranoid Android
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.settings">
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
</permissions>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View File

@@ -0,0 +1,11 @@
<!-- drawable/ic_auto_hbm_tile.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#000"
android:pathData="M11,4V1H13V4ZM11,23V20H13V23ZM20,13V11H23V13ZM1,13V11H4V13ZM18.7,6.7 L17.3,5.3 19.05,3.5 20.5,4.95ZM4.95,20.5 L3.5,19.05 5.3,17.3 6.7,18.7ZM19.05,20.5 L17.3,18.7 18.7,17.3 20.5,19.05ZM5.3,6.7 L3.5,4.95 4.95,3.5 6.7,5.3ZM12,18Q9.5,18 7.75,16.25Q6,14.5 6,12Q6,9.5 7.75,7.75Q9.5,6 12,6Q14.5,6 16.25,7.75Q18,9.5 18,12Q18,14.5 16.25,16.25Q14.5,18 12,18ZM12,16Q13.675,16 14.838,14.837Q16,13.675 16,12Q16,10.325 14.838,9.162Q13.675,8 12,8Q10.325,8 9.163,9.162Q8,10.325 8,12Q8,13.675 9.163,14.837Q10.325,16 12,16ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
</vector>

View File

@@ -0,0 +1,13 @@
<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="#000"
android:pathData="M20 4H4C2.9 4 2 4.9 2 6v12c0 1.1 0.9 2 2 2h16c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm0 14H4V6h16v12z"/>
<path
android:fillColor="#000"
android:pathData="M8.29 15.71c-0.49-0.49-0.88-1.06-1.14-1.7c-0.26-0.64-0.4-1.32-0.4-2.01c0-1.35 0.52-2.69 1.53-3.72L7.05 7.05C5.68 8.41 5 10.21 5 12s0.68 3.59 2.06 4.94l1.23-1.23zM12 15.5c1.93 0 3.5-1.57 3.5-3.5S13.93 8.5 12 8.5S8.5 10.07 8.5 12s1.57 3.5 3.5 3.5zm0-5c0.83 0 1.5 0.67 1.5 1.5s-0.67 1.5-1.5 1.5s-1.5-0.67-1.5-1.5s0.67-1.5 1.5-1.5zm3.72 5.22l1.23 1.23C18.32 15.59 19 13.79 19 12s-0.68-3.59-2.06-4.94l-1.23 1.23c0.49 0.49 0.88 1.06 1.14 1.7c0.26 0.64 0.4 1.32 0.4 2.01c0 1.35-0.52 2.69-1.53 3.72z"/>
</vector>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018-2022 crDroid Android Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0" >
<path
android:fillColor="?android:attr/colorControlNormal"
android:pathData="M24 4C12.972 4 4 12.972 4 24s8.972 20 20 20s20 -8.972 20 -20S35.028 4 24 4zM32.5 25.5h-17c-0.829 0 -1.5 -0.671 -1.5 -1.5s0.671 -1.5 1.5 -1.5h17c0.829 0 1.5 0.671 1.5 1.5S33.329 25.5 32.5 25.5z" />
</vector>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018-2022 crDroid Android Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0" >
<path
android:fillColor="?android:attr/colorControlNormal"
android:pathData="M24 4C12.972 4 4 12.972 4 24s8.972 20 20 20s20 -8.972 20 -20S35.028 4 24 4zM32.5 25.5h-7v7c0 0.829 -0.672 1.5 -1.5 1.5s-1.5 -0.671 -1.5 -1.5v-7h-7c-0.828 0 -1.5 -0.671 -1.5 -1.5s0.672 -1.5 1.5 -1.5h7v-7c0 -0.829 0.672 -1.5 1.5 -1.5s1.5 0.671 1.5 1.5v7h7c0.828 0 1.5 0.671 1.5 1.5S33.328 25.5 32.5 25.5z" />
</vector>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 Havoc-OS
2022 DerpFest
SPDX-License-Identifier: Apache-2.0
-->
<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" >
<path
android:fillColor="?android:attr/colorControlNormal"
android:pathData="M14.123456746339798,12.042328000068665 c0,-0.7430335164070133 -0.6079365134239197,-1.3509700298309326 -1.3509700298309326,-1.3509700298309326 s-1.3509700298309326,0.6079365134239197 -1.3509700298309326,1.3509700298309326 s0.6079365134239197,1.3509700298309326 1.3509700298309326,1.3509700298309326 s1.3509700298309326,-0.6079365134239197 1.3509700298309326,-1.3509700298309326 zM12.772486716508865,5.962962865829468 c-3.3571605241298674,0 -6.079365134239197,2.7222046101093293 -6.079365134239197,6.079365134239197 L4.66666653752327,12.042328000068665 l2.7019400596618652,2.7019400596618652 l2.7019400596618652,-2.7019400596618652 L8.044091612100601,12.042328000068665 c0,-2.6141270077228547 2.1142680966854095,-4.728395104408264 4.728395104408264,-4.728395104408264 s4.728395104408264,2.1142680966854095 4.728395104408264,4.728395104408264 s-2.1142680966854095,4.728395104408264 -4.728395104408264,4.728395104408264 c-1.0199823725223542,0 -1.965661393404007,-0.3309876573085789 -2.742469160556793,-0.8781305193901066 l-0.9591887211799618,0.9726984214782719 C10.097566057443618,17.648853623867033 11.380987585783004,18.12169313430786 12.772486716508865,18.12169313430786 c3.3571605241298674,0 6.079365134239197,-2.7222046101093293 6.079365134239197,-6.079365134239197 s-2.7222046101093293,-6.079365134239197 -6.079365134239197,-6.079365134239197 z" />
</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>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M480,680Q397,680 338.5,621.5Q280,563 280,480Q280,397 338.5,338.5Q397,280 480,280Q563,280 621.5,338.5Q680,397 680,480Q680,563 621.5,621.5Q563,680 480,680ZM73.33,517.88Q57.33,517.88 46.39,506.9Q35.46,495.93 35.46,479.88Q35.46,463.83 46.39,452.98Q57.33,442.12 73.33,442.12L166.67,442.12Q182.67,442.12 193.61,453.1Q204.54,464.07 204.54,480.12Q204.54,496.17 193.61,507.02Q182.67,517.88 166.67,517.88L73.33,517.88ZM793.33,517.88Q777.33,517.88 766.39,506.9Q755.46,495.93 755.46,479.88Q755.46,463.83 766.39,452.98Q777.33,442.12 793.33,442.12L886.67,442.12Q902.67,442.12 913.61,453.1Q924.54,464.07 924.54,480.12Q924.54,496.17 913.61,507.02Q902.67,517.88 886.67,517.88L793.33,517.88ZM479.88,204.54Q463.83,204.54 452.98,193.61Q442.12,182.67 442.12,166.67L442.12,73.33Q442.12,57.33 453.1,46.39Q464.07,35.46 480.12,35.46Q496.17,35.46 507.02,46.39Q517.88,57.33 517.88,73.33L517.88,166.67Q517.88,182.67 506.9,193.61Q495.93,204.54 479.88,204.54ZM479.88,924.54Q463.83,924.54 452.98,913.61Q442.12,902.67 442.12,886.67L442.12,793.33Q442.12,777.33 453.1,766.39Q464.07,755.46 480.12,755.46Q496.17,755.46 507.02,766.39Q517.88,777.33 517.88,793.33L517.88,886.67Q517.88,902.67 506.9,913.61Q495.93,924.54 479.88,924.54ZM232.15,285.43L179.81,234.09Q168.46,223.07 168.87,207.17Q169.28,191.27 179.74,179.74Q190.95,168.46 206.77,168.46Q222.59,168.46 233.85,179.81L285.43,232.15Q295.78,243.42 295.74,258.63Q295.7,273.83 285.43,284.86Q274.91,295.88 259.21,295.95Q243.5,296.02 232.15,285.43ZM726.15,780.19L674.57,727.85Q664.22,716.58 664.22,701.08Q664.22,685.57 674.91,674.81Q685.59,663.46 701.05,663.62Q716.5,663.79 727.85,674.81L780.19,725.91Q791.54,736.93 791.13,752.83Q790.72,768.73 780.26,780.26Q769.05,791.54 753.23,791.54Q737.41,791.54 726.15,780.19ZM674.81,285.43Q663.46,274.41 663.62,258.95Q663.79,243.5 674.81,232.15L725.91,179.81Q736.93,168.46 752.83,168.87Q768.73,169.28 780.26,179.74Q791.54,190.95 791.54,206.77Q791.54,222.59 780.19,233.85L727.85,285.43Q717.25,295.78 701.8,295.74Q686.35,295.7 674.81,285.43ZM179.74,780.26Q168.46,769.05 168.46,753.23Q168.46,737.41 179.81,726.15L232.15,674.57Q243.29,663.55 258.61,663.55Q273.93,663.55 284.93,674.57Q296.54,685.59 296.38,701.05Q296.21,716.5 285.19,727.85L234.09,780.19Q223.07,791.54 207.17,791.13Q191.27,790.72 179.74,780.26Z"/>
</vector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M16.41,18.59L15,20L7,12L15,4L16.41,5.41L9.83,12"
android:fillColor="?android:attr/colorAccent"
android:fillType="evenOdd"/>
</vector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M7.59,5.41L9,4L17,12L9,20L7.59,18.59L14.17,12"
android:fillColor="?android:attr/colorAccent"
android:fillType="evenOdd"/>
</vector>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="6dp"
android:height="6dp"
android:viewportWidth="6"
android:viewportHeight="6">
<path
android:pathData="M3,0C4.65686,0 6,1.34315 6,3C6,4.65686 4.65685,6 3,6C1.34315,6 0,4.65685 0,3C0,1.34315 1.34315,0 3,0Z"
android:fillColor="?android:attr/colorAccent"
android:fillType="evenOdd"/>
</vector>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="6dp"
android:height="6dp"
android:viewportWidth="6"
android:viewportHeight="6">
<path
android:pathData="M3,0C4.65686,0 6,1.34315 6,3C6,4.65686 4.65685,6 3,6C1.34315,6 0,4.65685 0,3C0,1.34315 1.34315,0 3,0Z"
android:fillColor="?android:attr/colorAccent"
android:fillAlpha="0.24"
android:fillType="evenOdd"/>
</vector>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<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/colorAccent">
<path
android:fillColor="#ff000000"
android:pathData="M17 1.01L7 1C5.9 1 5 1.9 5 3v4h2V3h10v18H7v-4H5v4c0 1.1 0.9 2 2 2h10c1.1 0 2-0.9 2-2V3c0-1.1-0.9-1.99-2-1.99Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M1 8v1.56h1.56v6.22H4.1V8H1Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M4.89 8v1.56H8v1.55H6.44c-0.4 0-0.8 0.17-1.1 0.46-0.29 0.29-0.45 0.68-0.45 1.1v3.1h4.67v-1.55H6.44v-1.55H8c0.41 0 0.8-0.17 1.1-0.46 0.3-0.3 0.46-0.69 0.46-1.1V9.56c0-0.42-0.17-0.81-0.46-1.1C8.8 8.16 8.41 8 8 8H4.89Z"/>
<path
android:fillColor="#ff000000"
android:strokeColor="#ff000000"
android:strokeWidth="1"
android:pathData="M11.89 9.06h-0.5v0.5 4.66 0.5h0.5 1.55 0.5v-0.5-4.66-0.5h-0.5-1.55ZM11.14 8.8c0.2-0.2 0.47-0.31 0.75-0.31h1.55c0.28 0 0.55 0.11 0.75 0.3 0.2 0.2 0.31 0.48 0.31 0.76v4.66c0 0.28-0.11 0.55-0.3 0.75-0.2 0.2-0.48 0.3-0.76 0.3H11.9c-0.28 0-0.55-0.1-0.75-0.3-0.2-0.2-0.3-0.47-0.3-0.75V9.56c0-0.28 0.1-0.55 0.3-0.75Z"/>
</vector>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<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/colorAccent">
<path
android:fillColor="#ff000000"
android:pathData="M6.55 8c-0.4 0-0.8 0.16-1.1 0.46C5.17 8.75 5 9.14 5 9.56v4.66c0 0.4 0.16 0.8 0.46 1.1 0.29 0.29 0.68 0.45 1.1 0.45H8.1c0.41 0 0.8-0.16 1.1-0.46 0.29-0.29 0.45-0.68 0.45-1.1v-1.55c0-0.41-0.16-0.8-0.45-1.1-0.3-0.29-0.69-0.45-1.1-0.45H6.55V9.55h3.11V8h-3.1Zm0 4.66h1.56v1.56H6.55v-1.56Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M12 8c-0.42 0-0.81 0.16-1.1 0.46-0.3 0.29-0.46 0.68-0.46 1.1v4.66c0 0.4 0.16 0.8 0.45 1.1 0.3 0.29 0.69 0.45 1.1 0.45h1.56c0.4 0 0.8-0.16 1.1-0.46 0.29-0.29 0.45-0.68 0.45-1.1V9.56c0-0.4-0.16-0.8-0.46-1.1C14.35 8.17 13.96 8 13.54 8H12Zm0 1.55h1.55v4.67h-1.56V9.55Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M17 1.01L7 1C5.9 1 5 1.9 5 3v4h2V3h10v18H7v-4H5v4c0 1.1 0.9 2 2 2h10c1.1 0 2-0.9 2-2V3c0-1.1-0.9-1.99-2-1.99Z"/>
</vector>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<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/colorAccent">
<path
android:fillColor="#ff000000"
android:pathData="M17 1.01L7 1C5.9 1 5 1.9 5 3v4h2V3h10v18H7v-4H5v4c0 1.1 0.9 2 2 2h10c1.1 0 2-0.9 2-2V3c0-1.1-0.9-1.99-2-1.99Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M8.1 15.77c0.42 0 0.82-0.16 1.1-0.46 0.3-0.29 0.46-0.68 0.46-1.1V9.56c0-0.4-0.16-0.8-0.45-1.1C8.9 8.17 8.52 8 8.1 8H6.55c-0.4 0-0.8 0.16-1.1 0.46C5.17 8.75 5 9.14 5 9.56v1.55c0 0.41 0.16 0.8 0.46 1.1 0.29 0.29 0.68 0.45 1.1 0.45H8.1v1.56H5v1.55h3.1Zm0-4.66H6.56V9.55h1.56v1.56Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M12 8c-0.42 0-0.81 0.16-1.1 0.46-0.3 0.29-0.46 0.68-0.46 1.1v4.66c0 0.4 0.16 0.8 0.45 1.1 0.3 0.29 0.69 0.45 1.1 0.45h1.56c0.4 0 0.8-0.16 1.1-0.46 0.29-0.29 0.45-0.68 0.45-1.1V9.56c0-0.4-0.16-0.8-0.46-1.1C14.35 8.17 13.96 8 13.54 8H12Zm0 1.55h1.55v4.67h-1.56V9.55Z"/>
</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="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorAccent">
<path
android:fillColor="#ff000000"
android:pathData="M17 3.01V15l2 2V3.01c0-1.1-0.9-1.99-2-1.99L7 1C6 0.99 5 1.99 5 2.99L7 5V3.01h10Zm4.2 18.19L19 19.01l-2-2-10-10-2-2-2.19-2.19L1.4 4.23 5 7.84v13.17c0 1.1 0.9 2 2 2h10c0.85 0 1.58-0.55 1.87-1.3l0.91 0.91 1.41-1.42ZM17 21.01H7L17 21v-0.99 1Zm-10 0V9.84l10 10.17V21L7 21.01Z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<!-- drawable/ic_saturation_tile -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#000"
android:pathData="M17.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,9A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 17.5,12M14.5,8A1.5,1.5 0 0,1 13,6.5A1.5,1.5 0 0,1 14.5,5A1.5,1.5 0 0,1 16,6.5A1.5,1.5 0 0,1 14.5,8M9.5,8A1.5,1.5 0 0,1 8,6.5A1.5,1.5 0 0,1 9.5,5A1.5,1.5 0 0,1 11,6.5A1.5,1.5 0 0,1 9.5,8M6.5,12A1.5,1.5 0 0,1 5,10.5A1.5,1.5 0 0,1 6.5,9A1.5,1.5 0 0,1 8,10.5A1.5,1.5 0 0,1 6.5,12M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A1.5,1.5 0 0,0 13.5,19.5C13.5,19.11 13.35,18.76 13.11,18.5C12.88,18.23 12.73,17.88 12.73,17.5A1.5,1.5 0 0,1 14.23,16H16A5,5 0 0,0 21,11C21,6.58 16.97,3 12,3Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<!-- drawable/music_clef_bass.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path android:fillColor="#000" android:pathData="M18.5 5A1.5 1.5 0 1 1 17 6.5A1.5 1.5 0 0 1 18.5 5M18.5 11A1.5 1.5 0 1 1 17 12.5A1.5 1.5 0 0 1 18.5 11M10 4A5 5 0 0 0 5 9V10A2 2 0 1 0 7.18 8A3 3 0 0 1 10 6A4 4 0 0 1 14 10C14 13.59 11.77 16.19 7 18.2L7.76 20.04C13.31 17.72 16 14.43 16 10A6 6 0 0 0 10 4Z" />
</vector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<group
android:scaleX="5.50297"
android:scaleY="5.52411"
android:translateX="-59.0634"
android:translateY="-57.934">
<path
android:fillColor="?android:attr/colorAccent"
android:fillType="evenOdd"
android:pathData="M10.961 13.514c0 -1.078 0.874 -1.952 1.953 -1.952 0.555 0 1.057 0.232 1.412 0.604l-0.583 0.141c-0.236 -0.162 -0.522 -0.257 -0.829 -0.257 -0.809 0 -1.465 0.655 -1.465 1.464l0 0c0 0.135 -0.109 0.244 -0.244 0.244 -0.134 0 -0.244 -0.109 -0.244 -0.244l0 0zm3.417 0c0 -0.094 -0.009 -0.185 -0.026 -0.274l0.429 -0.297c0.055 0.181 0.085 0.373 0.085 0.571 0 0.135 -0.109 0.244 -0.244 0.244 -0.135 0 -0.244 -0.109 -0.244 -0.244l0 0zm-0.02 -1.097c0.102 -0.028 0.212 0.022 0.256 0.121 0.045 0.101 0.007 0.219 -0.085 0.275l0 0 -1.313 0.833c-0.011 0.008 -0.023 0.016 -0.035 0.023l-0.001 0 0 0c-0.013 0.008 -0.025 0.014 -0.039 0.02 -0.226 0.101 -0.491 0 -0.592 -0.226 -0.101 -0.225 0 -0.491 0.226 -0.592 0.026 -0.012 0.052 -0.021 0.079 -0.027l1.504 -0.427 0 0z"
android:strokeLineJoin="round"
android:strokeMiterLimit="1.41421" />
</group>
</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="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="?android:attr/colorAccent"
android:pathData="M2.546,5.862C4.557,2.772 8.042,0.727 12,0.727C15.958,0.727 19.443,2.772 21.454,5.862C21.584,5.947 21.674,6.086 21.694,6.246C22.697,7.931 23.273,9.899 23.273,12C23.273,18.222 18.222,23.273 12,23.273C5.778,23.273 0.727,18.222 0.727,12C0.727,9.899 1.303,7.931 2.306,6.246C2.326,6.086 2.416,5.947 2.546,5.862ZM7.784,18.049L5.229,18.049C6.602,19.585 8.49,20.651 10.62,20.977C9.503,20.219 8.537,19.221 7.784,18.049ZM18.771,18.049L16.31,18.049C15.564,19.211 14.608,20.202 13.503,20.957C15.583,20.61 17.424,19.555 18.771,18.049ZM13.235,18.049L10.86,18.049C11.207,18.703 11.606,19.307 12.047,19.852C12.488,19.307 12.888,18.703 13.235,18.049ZM6.086,13.083L2.983,13.083C3.101,14.074 3.379,15.016 3.79,15.882L6.713,15.882C6.393,15.001 6.178,14.062 6.086,13.083ZM21.017,13.083L18.008,13.083C17.916,14.062 17.702,15.001 17.382,15.882L20.21,15.882C20.621,15.016 20.899,14.074 21.017,13.083ZM14.67,13.083L9.424,13.083C9.503,14.057 9.689,14.997 9.962,15.882L14.132,15.882C14.406,14.997 14.591,14.057 14.67,13.083ZM6.78,7.939L3.877,7.939C3.418,8.854 3.109,9.858 2.983,10.917L6.086,10.917C6.184,9.872 6.422,8.871 6.78,7.939ZM20.123,7.939L17.315,7.939C17.672,8.871 17.91,9.872 18.008,10.917L21.017,10.917C20.891,9.858 20.582,8.854 20.123,7.939ZM14.076,7.939L10.019,7.939C9.714,8.877 9.509,9.877 9.424,10.917L14.67,10.917C14.585,9.877 14.38,8.877 14.076,7.939ZM10.62,3.023C8.574,3.336 6.751,4.333 5.393,5.772L7.901,5.772C8.637,4.677 9.56,3.742 10.62,3.023ZM13.503,3.043C14.551,3.759 15.465,4.687 16.193,5.772L18.607,5.772C17.276,4.361 15.499,3.376 13.503,3.043ZM12.047,4.148C11.647,4.643 11.281,5.187 10.957,5.772L13.138,5.772C12.814,5.187 12.448,4.643 12.047,4.148Z" />
</vector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<group
android:scaleX="0.879929"
android:scaleY="0.883309"
android:translateX="1.44085"
android:translateY="0.746289">
<path
android:fillColor="?android:attr/colorAccent"
android:fillType="evenOdd"
android:pathData="M20.764 21.26l-17.528 0c-1.07 0 -1.939 -0.868 -1.939 -1.939l0 -11.324c0 -1.07 0.869 -1.939 1.939 -1.939l4.534 0C7.982 5.01 8.91 4.22 10.02 4.22l3.96 0c1.11 0 2.037 0.79 2.25 1.838l4.534 0c1.07 0 1.939 0.869 1.939 1.939l0 11.324c0 1.071 -0.869 1.939 -1.939 1.939zM12 8.646c2.767 0 5.013 2.246 5.013 5.013 0 2.767 -2.246 5.013 -5.013 5.013 -2.767 0 -5.013 -2.246 -5.013 -5.013 0 -2.767 2.246 -5.013 5.013 -5.013zM3.674 7.257c0.624 0 1.131 0.507 1.131 1.131 0 0.624 -0.507 1.131 -1.131 1.131 -0.624 0 -1.131 -0.507 -1.131 -1.131 0 -0.624 0.507 -1.131 1.131 -1.131z"
android:strokeLineJoin="round"
android:strokeMiterLimit="1.41421" />
</group>
</vector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<group
android:scaleX="1.05866"
android:scaleY="1.05866"
android:translateX="-0.652026"
android:translateY="-0.703971">
<path
android:fillColor="?android:attr/colorAccent"
android:fillType="evenOdd"
android:pathData="M11.066 10.383l0 -2.773 -2.033 -2.033c-0.364 -0.365 -0.364 -0.956 0 -1.321 0.365 -0.364 0.956 -0.364 1.321 0l0.712 0.713 0 -1.111c0 -0.515 0.419 -0.934 0.934 -0.934 0.515 0 0.934 0.419 0.934 0.934l0 1.111 0.712 -0.713c0.365 -0.364 0.956 -0.364 1.321 0 0.364 0.365 0.364 0.956 0 1.321l-2.033 2.033 0 2.773 2.401 -1.387 0.744 -2.777c0.134 -0.498 0.646 -0.794 1.144 -0.66 0.498 0.133 0.794 0.646 0.66 1.143L17.622 7.676 18.584 7.12c0.447 -0.257 1.018 -0.104 1.276 0.342 0.258 0.446 0.104 1.018 -0.342 1.276l-0.962 0.555 0.973 0.261c0.498 0.133 0.794 0.646 0.661 1.144 -0.134 0.497 -0.646 0.793 -1.144 0.66L16.269 10.614 13.868 12l2.401 1.386 2.777 -0.744c0.498 -0.133 1.01 0.163 1.144 0.66 0.133 0.498 -0.163 1.011 -0.661 1.144l-0.973 0.261 0.962 0.555c0.446 0.258 0.6 0.83 0.342 1.276 -0.258 0.446 -0.829 0.599 -1.276 0.342l-0.962 -0.556 0.261 0.974c0.134 0.497 -0.162 1.01 -0.66 1.143 -0.498 0.134 -1.01 -0.162 -1.144 -0.66l-0.744 -2.777 -2.401 -1.387 0 2.886 2.033 2.034c0.364 0.364 0.364 0.956 0 1.32 -0.365 0.365 -0.956 0.365 -1.321 0l-0.712 -0.712 0 0.997c0 0.515 -0.419 0.934 -0.934 0.934 -0.515 0 -0.934 -0.419 -0.934 -0.934l0 -0.997 -0.712 0.712c-0.365 0.365 -0.956 0.365 -1.321 0 -0.364 -0.364 -0.364 -0.956 0 -1.32l2.033 -2.034 0 -2.886 -2.499 1.443 -0.744 2.778C7.689 18.335 7.177 18.631 6.679 18.498 6.181 18.365 5.885 17.852 6.019 17.354L6.28 16.381 5.416 16.88C4.969 17.137 4.398 16.984 4.14 16.538 3.882 16.092 4.036 15.52 4.482 15.262L5.346 14.764 4.372 14.503C3.875 14.369 3.579 13.857 3.712 13.359 3.846 12.861 4.358 12.565 4.856 12.699L7.633 13.443 10.132 12 7.633 10.557 4.856 11.301C4.358 11.435 3.846 11.139 3.712 10.641 3.579 10.143 3.875 9.631 4.372 9.497L5.346 9.236 4.482 8.738C4.036 8.48 3.882 7.908 4.14 7.462 4.398 7.016 4.969 6.863 5.416 7.12L6.28 7.619 6.019 6.646C5.885 6.148 6.181 5.635 6.679 5.502 7.177 5.369 7.689 5.665 7.823 6.162l0.744 2.778 2.499 1.443z"
android:strokeLineJoin="round"
android:strokeMiterLimit="1.41421" />
</group>
</vector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<group
android:scaleX="0.0930942"
android:scaleY="0.0934518"
android:translateX="19.4408"
android:translateY="9.49012">
<path
android:fillColor="?android:attr/colorAccent"
android:pathData="M0 57.121c-22.463 -12.601 -36.159 -9.862 -42.734 0 -6.574 9.862 -20.819 24.106 -42.186 6.574 -10.987 -9.015 -22.91 -21.121 -31.769 -31.918 -17.532 -21.367 -3.287 -35.612 6.575 -42.186 9.861 -6.575 12.601 -20.272 0 -42.734 -12.601 -22.463 -33.421 -16.437 -33.421 -16.437 0 0 -22.462 6.575 -32.324 48.213 -8.889 37.534 35.637 88.416 44.502 98.083 0 0 57.942 56.011 99.581 46.149 41.638 -9.861 48.212 -32.324 48.212 -32.324 0 0 6.027 -20.819 -16.436 -33.42"
android:strokeLineJoin="round"
android:strokeMiterLimit="1.41421" />
</group>
</vector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<group
android:scaleX="0.921041"
android:scaleY="0.924579"
android:translateX="0.752324"
android:translateY="0.893459">
<path
android:fillColor="?android:attr/colorAccent"
android:fillType="evenOdd"
android:pathData="M10.228 14.833c-0.006 0 -0.894 0.008 -1.83 1.069 -0.716 0.812 -1.981 2.473 -3.218 3.16 -0.07 0.041 -0.143 0.078 -0.218 0.111 -0.311 0.143 -0.619 0.217 -0.913 0.192l-0.015 0C3.63 19.362 3.252 19.254 2.924 19.066 1.702 18.44 1.782 17.099 1.782 17.099c0 -0.07 0.004 -0.139 0.01 -0.206C1.847 15.696 2.143 9.661 2.563 8.549 3.102 6.316 5.114 4.656 7.51 4.656l9.423 0c2.478 0 4.545 1.775 4.997 4.123 0.383 1.539 0.65 6.984 0.702 8.114 0.006 0.067 0.009 0.136 0.009 0.206 0 0 0.081 1.341 -1.142 1.967 -0.327 0.188 -0.706 0.296 -1.109 0.299l-0.015 0c-0.294 0.025 -0.602 -0.049 -0.914 -0.192 -0.074 -0.033 -0.147 -0.07 -0.218 -0.111 -1.236 -0.687 -2.502 -2.348 -3.217 -3.16 -0.936 -1.061 -1.824 -1.069 -1.83 -1.069l-3.968 0zM8.247 6.379c1.935 0 3.507 1.571 3.507 3.507 0 1.935 -1.572 3.507 -3.507 3.507 -1.936 0 -3.507 -1.572 -3.507 -3.507 0 -1.936 1.571 -3.507 3.507 -3.507zm9.138 4.16c0.454 0 0.823 0.369 0.823 0.823 0 0.454 -0.369 0.823 -0.823 0.823 -0.454 0 -0.823 -0.369 -0.823 -0.823 0 -0.454 0.369 -0.823 0.823 -0.823zm-8.421 0.07l0 0.814c0 0.399 -0.324 0.723 -0.723 0.723 -0.399 0 -0.723 -0.324 -0.723 -0.723l0 -0.814 -0.815 0C6.304 10.609 5.98 10.285 5.98 9.886 5.98 9.487 6.304 9.163 6.703 9.163l0.815 0 0 -0.815c0 -0.399 0.324 -0.723 0.723 -0.723 0.399 0 0.723 0.324 0.723 0.723l0 0.815 0.815 0c0.399 0 0.723 0.324 0.723 0.723 0 0.399 -0.324 0.722 -0.723 0.723l-0.815 0zm10.72 -0.723c0 0.454 -0.369 0.823 -0.823 0.823 -0.454 0 -0.823 -0.369 -0.823 -0.823 0 -0.455 0.369 -0.823 0.823 -0.823 0.454 0 0.823 0.368 0.823 0.823zm-2.952 0c0 0.454 -0.369 0.823 -0.823 0.823 -0.455 0 -0.823 -0.369 -0.823 -0.823 0 -0.455 0.368 -0.823 0.823 -0.823 0.454 0 0.823 0.368 0.823 0.823zm0.653 -2.3c0.454 0 0.823 0.369 0.823 0.824 0 0.454 -0.369 0.823 -0.823 0.823 -0.454 0 -0.823 -0.369 -0.823 -0.823 0 -0.455 0.369 -0.824 0.823 -0.824z"
android:strokeLineJoin="round"
android:strokeMiterLimit="1.41421" />
</group>
</vector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<group
android:scaleX="1.20862"
android:scaleY="1.21327"
android:translateX="-0.686496"
android:translateY="-0.544151">
<path
android:fillColor="?android:attr/colorAccent"
android:fillType="evenOdd"
android:pathData="M13.29 18.046l-8.091 0c-1.277 0 -2.315 -1.037 -2.315 -2.315l0 -4.631c0 -1.277 1.038 -2.315 2.315 -2.315l8.091 0c1.272 0 2.305 1.027 2.315 2.296l1.307 -0.754c0.065 -0.038 0.135 -0.067 0.21 -0.086l0.002 0 0 0 0 0 0 0c0.062 -0.015 0.126 -0.023 0.188 -0.023l0.001 0 0.003 0c0.146 0.001 0.283 0.041 0.401 0.11 0.11 0.065 0.205 0.156 0.276 0.272 0.043 0.071 0.076 0.15 0.096 0.233l0 0.002c0.014 0.059 0.02 0.119 0.02 0.178l0 0.001 0 4.803 0 0.001c0 0.059 -0.006 0.119 -0.02 0.178l0 0.001 0 0 0 0 0 0.001c-0.02 0.084 -0.053 0.162 -0.096 0.233 -0.071 0.116 -0.166 0.208 -0.276 0.272 -0.118 0.07 -0.255 0.11 -0.401 0.11l0 0 0 0 -0.003 0 -0.001 0c-0.062 0 -0.126 -0.007 -0.188 -0.023l0 0 0 0 0 0 -0.002 0c-0.075 -0.018 -0.145 -0.047 -0.21 -0.085L15.605 15.75c-0.01 1.269 -1.043 2.296 -2.315 2.296zm-1.773 -4.63c0 -0.107 -0.027 -0.214 -0.083 -0.313l0 0 0 0 -0.001 -0.002 -0.001 0C11.409 13.06 11.381 13.023 11.349 12.988 11.305 12.94 11.254 12.9 11.198 12.869L7.919 10.975C7.867 10.945 7.811 10.922 7.752 10.908l-0.001 -0.001 0 0 0 0 0 0C7.702 10.895 7.652 10.889 7.602 10.889l0 0 -0.002 0 -0.001 0 0 0C7.484 10.89 7.376 10.921 7.283 10.976 7.196 11.027 7.12 11.099 7.064 11.191 7.03 11.247 7.004 11.309 6.989 11.375l0 0.001 0 0 0 0 0 0.001c-0.011 0.046 -0.016 0.094 -0.016 0.141l0 0 0 3.795 0 0.001c0 0.047 0.005 0.094 0.016 0.141l0 0.001c0.015 0.066 0.041 0.128 0.075 0.185 0.056 0.091 0.132 0.163 0.219 0.214 0.093 0.055 0.201 0.087 0.316 0.087l0.003 0 0 0c0.05 0 0.1 -0.006 0.149 -0.018l0 0 0 0 0 0 0.001 0c0.059 -0.015 0.115 -0.038 0.167 -0.068l3.279 -1.893c0.056 -0.032 0.107 -0.072 0.151 -0.119 0.032 -0.035 0.06 -0.073 0.083 -0.113l0.001 -0.001 0.001 -0.001 0 -0.001 0 0c0.056 -0.098 0.083 -0.206 0.083 -0.312zM6.026 4.848c0.955 0 1.731 0.776 1.731 1.731C7.757 7.535 6.981 8.31 6.026 8.31 5.07 8.31 4.295 7.535 4.295 6.579 4.295 5.624 5.07 4.848 6.026 4.848Zm4.986 -2.216c1.567 0 2.839 1.272 2.839 2.839 0 1.567 -1.272 2.839 -2.839 2.839 -1.567 0 -2.839 -1.272 -2.839 -2.839 0 -1.567 1.272 -2.839 2.839 -2.839z"
android:strokeLineJoin="round"
android:strokeMiterLimit="1.41421" />
</group>
</vector>

View File

@@ -0,0 +1,11 @@
<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/colorAccent">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M19.58 9.42l1.54-1.54C21.68 9.14 22 10.53 22 12c0 5.52-4.48 10-10 10S2 17.52 2 12 6.48 2 12 2c1.46 0 2.85 0.32 4.11 0.89l-1.53 1.53C13.77 4.15 12.9 4 12 4c-4.41 0-8 3.59-8 8s3.59 8 8 8 8-3.59 8-8c0-0.9-0.15-1.77-0.42-2.58Zm-8.35 4.43c-0.25-0.1-0.46-0.25-0.65-0.43-0.18-0.19-0.33-0.4-0.43-0.65-0.1-0.24-0.15-0.5-0.15-0.75 0-0.27 0.05-0.52 0.15-0.76 0.1-0.24 0.25-0.46 0.43-0.65L19 5l-5.61 8.42c-0.19 0.18-0.4 0.33-0.65 0.43-0.24 0.1-0.5 0.15-0.76 0.15s-0.51-0.05-0.75-0.15Z"/>
</vector>

View File

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

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foregroundGravity="center_horizontal">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="320dp"
android:scaleType="centerCrop"
android:cropToPadding="true"
android:contentDescription="@null" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="320dp"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="48dp">
<FrameLayout
android:id="@+id/arrow_previous"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:paddingLeft="24dp"
android:layout_gravity="center_vertical|left"
android:contentDescription="@string/image_preview_previous_page_content_description">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_image_preview_arrow_left"/>
</FrameLayout>
<LinearLayout
android:id="@+id/viewGroup"
android:layout_width="fill_parent"
android:layout_height="48dp"
android:gravity="center"/>
<FrameLayout
android:id="@+id/arrow_next"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:paddingRight="24dp"
android:layout_gravity="center_vertical|right"
android:contentDescription="@string/image_preview_next_page_content_description">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_image_preview_arrow_right"/>
</FrameLayout>
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017-2022 crDroid Android Project
SPDX-License-Identifier: Apache-2.0
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/activatedBackgroundIndicator"
android:clipToPadding="false">
<LinearLayout
android:id="@android:id/icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="-4dp"
android:minWidth="60dp"
android:gravity="start|center_vertical"
android:orientation="horizontal"
android:paddingEnd="12dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<com.android.internal.widget.PreferenceImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="48dp"
android:maxHeight="48dp" />
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="10"
android:ellipsize="end" />
<RelativeLayout
android:id="@+id/value_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/summary"
android:layout_alignStart="@android:id/title" >
<TextView
android:id="@+id/value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="1"
android:ellipsize="end" />
<ImageView
android:id="@+id/reset"
android:src="@drawable/ic_custom_seekbar_reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_toEndOf="@id/value"
android:layout_centerVertical="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/seekbar_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/value_frame"
android:layout_alignStart="@android:id/title" >
<ImageView
android:id="@+id/minus"
android:src="@drawable/ic_custom_seekbar_minus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" />
<ImageView
android:id="@+id/plus"
android:src="@drawable/ic_custom_seekbar_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" />
<LinearLayout
android:id="@+id/seekbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_toEndOf="@id/minus"
android:layout_toStartOf="@id/plus"
android:layout_centerVertical="true" />
</RelativeLayout>
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="end|center_vertical"
android:paddingStart="16dp"
android:orientation="vertical" />
</LinearLayout>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/refresh_rv_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingBottom="4dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="4dp">
<ImageView
android:id="@+id/app_icon"
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="@android:dimen/app_icon_size"
android:layout_marginEnd="8dp"
android:contentDescription="@null"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="2dp"
android:ellipsize="marquee"
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Medium"
android:textColor="?android:attr/textColorPrimary" />
<Spinner
android:id="@+id/app_mode"
android:layout_marginTop="2dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:id="@+id/state"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:scaleType="centerInside"
android:src="@drawable/ic_refresh_default" />
</LinearLayout>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/thermal_rv_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingBottom="4dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="4dp">
<ImageView
android:id="@+id/app_icon"
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="@android:dimen/app_icon_size"
android:layout_marginEnd="8dp"
android:contentDescription="@null"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="2dp"
android:ellipsize="marquee"
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Medium"
android:textColor="?android:attr/textColorPrimary" />
<Spinner
android:id="@+id/app_mode"
android:layout_marginTop="2dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:id="@+id/state"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:scaleType="centerInside"
android:src="@drawable/ic_thermal_default" />
</LinearLayout>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2025 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add_tile"
android:title="@string/tile_add" />
</menu>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add_tile"
android:title="@string/tile_add" />
</menu>

Binary file not shown.

119
parts/res/values/arrays.xml Normal file
View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 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.
-->
<resources>
<!-- 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

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018-2022 crDroid Android Project
SPDX-License-Identifier: Apache-2.0
-->
<resources>
<!-- Base attributes available to CustomSeekBarPreference. -->
<declare-styleable name="CustomSeekBarPreference">
<attr name="defaultValueText" format="string" />
<attr name="interval" format="integer" />
<attr name="showSign" format="boolean" />
<attr name="units" format="string|reference" />
<attr name="continuousUpdates" format="boolean" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Xiaomi Settings -->
<string name="app_title">Xiaomi Settings</string>
<string name="tile_add">Add tile</string>
<string name="tile_added">Tile added</string>
<string name="tile_not_added">Tile not added</string>
<string name="tile_already_added">Tile already added</string>
<string name="tile_on">On</string>
<string name="tile_off">Off</string>
<!-- Thermal profiles -->
<string name="thermal_title">Thermal profiles</string>
<string name="thermal_summary">Adjust per-app thermal profiles for optimum performance</string>
<string name="thermal_default">No optimization</string>
<string name="thermal_benchmark">Performance</string>
<string name="thermal_browser">Browser</string>
<string name="thermal_camera">Camera</string>
<string name="thermal_dialer">Dialer</string>
<string name="thermal_gaming">Gaming</string>
<string name="thermal_streaming">Streaming</string>
<!-- Per-app refresh rate -->
<string name="refresh_title">Per-app refresh rate</string>
<string name="refresh_summary">Set the maximum refresh rate for a specific application</string>
<string name="refresh_default">Default</string>
<string name="refresh_standard">60 Hz</string>
<string name="refresh_high">90 Hz</string>
<string name="refresh_extreme">120 Hz</string>
<!-- Clear Speaker -->
<string name="clear_speaker_title">Clear speaker</string>
<string name="clear_speaker_summary">Play a 30-second audio to clear the speaker</string>
<string name="clear_speaker_description">Run this feature once or twice if you find that your speaker is lightly blocked by dust. Set media volume to maximum.\n\nIf the speaker is blocked heavily, run this feature 2-5 times while shaking your device with the speaker facing downwards.</string>
<!-- Saturation -->
<string name="saturation_title">Display Saturation</string>
<string name="saturation_summary">Control the saturation level of the display</string>
<string name="saturation_footer_summary">Changing the color mode in display settings will override the saturation level, requiring it to be reapplied.</string>
<!-- Image preview -->
<string name="image_preview_content_description">Preview</string>
<string name="image_preview_next_page_content_description">Next</string>
<string name="image_preview_previous_page_content_description">Previous</string>
<!-- Custom seekbar -->
<string name="custom_seekbar_value">Value: <xliff:g id="v">%s</xliff:g></string>
<string name="custom_seekbar_default_value">by default</string>
<string name="custom_seekbar_default_value_to_set">Default value: <xliff:g id="v">%s</xliff:g>\nLong tap to set</string>
<string name="custom_seekbar_default_value_is_set">Default value is set</string>
<!-- Auto HBM -->
<string name="auto_hbm_title">Automatic High Brightness Mode (HBM)</string>
<string name="auto_hbm_main_switch_title">Use automatic HBM</string>
<string name="tile_auto_hbm">Auto HBM</string>
<string name="tile_hbm_on">Enabled</string>
<string name="tile_hbm_off">Disabled</string>
<string name="tile_hbm">HBM</string>
<string name="tile_hbm_unavailable">HBM Unavailable</string>
<string name="tile_auto_hbm_or_brightness_enabled">Auto HBM or Auto Brightness is enabled</string>
<string name="hbm_mode_title">High Brightness Mode</string>
<string name="hbm_mode_notification">High Brightness Mode is now activated. Long time usage may damage your display.</string>
<string name="auto_hbm_summary">Automatically enable high brightness mode based on sunlight intensity (lux)</string>
<string name="auto_hbm_threshold_title">Light threshold</string>
<string name="auto_hbm_threshold_summary">Define the light intensity threshold in lux units at which HBM is activated</string>
<string name="auto_hbm_enable_time_title">Enable time</string>
<string name="auto_hbm_enable_time_summary">Duration in seconds that the light threshold must be continuously met to activate HBM</string>
<string name="auto_hbm_disable_time_title">Disable time</string>
<string name="auto_hbm_disable_time_summary">Duration in seconds for which HBM is deactivated if the light threshold falls below the set level</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,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<com.android.settingslib.widget.TopIntroPreference
android:key="auto_hbm_intro"
android:title="@string/auto_hbm_summary" />
<com.android.settingslib.widget.MainSwitchPreference
android:key="auto_hbm"
android:title="@string/auto_hbm_main_switch_title"
android:defaultValue="false" />
<com.android.settingslib.widget.UsageProgressBarPreference
android:key="current_lux_level"
android:selectable="false" />
<org.lineageos.settings.CustomSeekBarPreference
android:key="auto_hbm_threshold"
android:title="@string/auto_hbm_threshold_title"
android:summary="@string/auto_hbm_threshold_summary"
android:max="60000"
android:min="2000"
settings:units=""
android:defaultValue="20000" />
<org.lineageos.settings.CustomSeekBarPreference
android:key="auto_hbm_enable_time"
android:title="@string/auto_hbm_enable_time_title"
android:summary="@string/auto_hbm_enable_time_summary"
android:max="10"
android:min="0"
settings:units=""
android:defaultValue="0" />
<org.lineageos.settings.CustomSeekBarPreference
android:key="auto_hbm_disable_time"
android:title="@string/auto_hbm_disable_time_title"
android:summary="@string/auto_hbm_disable_time_summary"
android:max="10"
android:min="0"
settings:units=""
android:defaultValue="1" />
</PreferenceScreen>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/clear_speaker_title">
<SwitchPreference
android:key="clear_speaker_pref"
android:title="@string/clear_speaker_title"
android:icon="@drawable/ic_clear_speaker"
android:summary="@string/clear_speaker_summary"/>
<com.android.settingslib.widget.FooterPreference
android:key="footer_preference"
android:title="@string/clear_speaker_description"
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

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018-2014 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<com.android.settingslib.widget.LayoutPreference
android:key="saturation_preview"
android:layout="@layout/image_preview_layout"
android:selectable="false" />
<org.lineageos.settings.CustomSeekBarPreference
android:key="saturation"
android:max="200"
android:min="0"
android:defaultValue="100"
settings:continuousUpdates="true" />
<com.android.settingslib.widget.FooterPreference
android:key="saturation_footer"
android:title="@string/saturation_footer_summary"
android:selectable="false" />
</PreferenceScreen>

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2015 The CyanogenMod Project
* 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.
*/
package org.lineageos.settings;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import org.lineageos.settings.thermal.ThermalUtils;
import org.lineageos.settings.refreshrate.RefreshUtils;
public class BootCompletedReceiver extends BroadcastReceiver {
private static final boolean DEBUG = false;
private static final String TAG = "XiaomiParts";
@Override
public void onReceive(final Context context, Intent intent) {
if (DEBUG) Log.i(TAG, "Received intent: " + intent.getAction());
switch (intent.getAction()) {
case Intent.ACTION_LOCKED_BOOT_COMPLETED:
onLockedBootCompleted(context);
break;
case Intent.ACTION_BOOT_COMPLETED:
onBootCompleted(context);
break;
}
}
private static void onLockedBootCompleted(Context context) {
ThermalUtils.startService(context);
RefreshUtils.startService(context);
}
private static void onBootCompleted(Context context) {
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 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.settings;
public class Constants {
// AutoHbm
public static final String KEY_AUTO_HBM = "auto_hbm";
public static final String KEY_AUTO_HBM_THRESHOLD = "auto_hbm_threshold";
public static final String KEY_AUTO_HBM_ENABLE_TIME = "auto_hbm_enable_time";
public static final String KEY_AUTO_HBM_DISABLE_TIME = "auto_hbm_disable_time";
public static final String KEY_CURRENT_LUX_LEVEL = "current_lux_level";
public static final String NODE_BRIGHTNESS = "/sys/class/backlight/panel0-backlight/brightness";
// Saturation
public static final String KEY_SATURATION = "saturation";
public static final String KEY_SATURATION_PREVIEW = "saturation_preview";
}

View File

@@ -0,0 +1,368 @@
/*
* Copyright (C) 2024 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.settings;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.*;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import org.lineageos.settings.R;
public class CustomSeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener {
protected final String TAG = getClass().getName();
private static final String SETTINGS_NS = "http://schemas.android.com/apk/res/com.android.settings";
protected static final String ANDROIDNS = "http://schemas.android.com/apk/res/android";
protected int mInterval = 1;
protected boolean mShowSign = false;
protected String mUnits = "";
protected boolean mContinuousUpdates = false;
protected int mMinValue = 0;
protected int mMaxValue = 100;
protected boolean mDefaultValueExists = false;
protected int mDefaultValue;
protected boolean mDefaultValueTextExists = false;
protected String mDefaultValueText;
protected int mValue;
protected TextView mValueTextView;
protected ImageView mResetImageView;
protected ImageView mMinusImageView;
protected ImageView mPlusImageView;
protected SeekBar mSeekBar;
protected boolean mTrackingTouch = false;
protected int mTrackingValue;
public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomSeekBarPreference);
try {
mShowSign = a.getBoolean(R.styleable.CustomSeekBarPreference_showSign, mShowSign);
String units = a.getString(R.styleable.CustomSeekBarPreference_units);
if (units != null)
mUnits = " " + units;
mContinuousUpdates = a.getBoolean(R.styleable.CustomSeekBarPreference_continuousUpdates, mContinuousUpdates);
String defaultValueText = a.getString(R.styleable.CustomSeekBarPreference_defaultValueText);
mDefaultValueTextExists = defaultValueText != null && !defaultValueText.isEmpty();
if (mDefaultValueTextExists) {
mDefaultValueText = defaultValueText;
}
} finally {
a.recycle();
}
try {
String newInterval = attrs.getAttributeValue(SETTINGS_NS, "interval");
if (newInterval != null)
mInterval = Integer.parseInt(newInterval);
} catch (Exception e) {
Log.e(TAG, "Invalid interval value", e);
}
mMinValue = attrs.getAttributeIntValue(SETTINGS_NS, "min", mMinValue);
mMaxValue = attrs.getAttributeIntValue(ANDROIDNS, "max", mMaxValue);
if (mMaxValue < mMinValue)
mMaxValue = mMinValue;
String defaultValue = attrs.getAttributeValue(ANDROIDNS, "defaultValue");
mDefaultValueExists = defaultValue != null && !defaultValue.isEmpty();
if (mDefaultValueExists) {
mDefaultValue = getLimitedValue(Integer.parseInt(defaultValue));
mValue = mDefaultValue;
} else {
mValue = mMinValue;
}
mSeekBar = new SeekBar(context, attrs);
setLayoutResource(R.layout.preference_custom_seekbar);
}
public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public CustomSeekBarPreference(Context context, AttributeSet attrs) {
this(context, attrs, TypedArrayUtils.getAttr(context,
androidx.preference.R.attr.preferenceStyle,
android.R.attr.preferenceStyle));
}
public CustomSeekBarPreference(Context context) {
this(context, null);
}
@Override
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
super.onDependencyChanged(dependency, disableDependent);
this.setShouldDisableView(true);
if (mSeekBar != null)
mSeekBar.setEnabled(!disableDependent);
if (mResetImageView != null)
mResetImageView.setEnabled(!disableDependent);
if (mPlusImageView != null)
mPlusImageView.setEnabled(!disableDependent);
if (mMinusImageView != null)
mMinusImageView.setEnabled(!disableDependent);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
try
{
// move our seekbar to the new view we've been given
ViewParent oldContainer = mSeekBar.getParent();
ViewGroup newContainer = (ViewGroup) holder.findViewById(R.id.seekbar);
if (oldContainer != newContainer) {
// remove the seekbar from the old view
if (oldContainer != null) {
((ViewGroup) oldContainer).removeView(mSeekBar);
}
// remove the existing seekbar (there may not be one) and add ours
newContainer.removeAllViews();
newContainer.addView(mSeekBar, ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
} catch (Exception ex) {
Log.e(TAG, "Error binding view: " + ex.toString());
}
mSeekBar.setMax(getSeekValue(mMaxValue));
mSeekBar.setProgress(getSeekValue(mValue));
mSeekBar.setEnabled(isEnabled());
mValueTextView = (TextView) holder.findViewById(R.id.value);
mResetImageView = (ImageView) holder.findViewById(R.id.reset);
mMinusImageView = (ImageView) holder.findViewById(R.id.minus);
mPlusImageView = (ImageView) holder.findViewById(R.id.plus);
updateValueViews();
mSeekBar.setOnSeekBarChangeListener(this);
mResetImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getContext(), getContext().getString(R.string.custom_seekbar_default_value_to_set, getTextValue(mDefaultValue)),
Toast.LENGTH_LONG).show();
}
});
mResetImageView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
setValue(mDefaultValue, true);
return true;
}
});
mMinusImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
setValue(mValue - mInterval, true);
}
});
mMinusImageView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue < mValue * 2 ? Math.floorDiv(mMaxValue + mMinValue, 2) : mMinValue, true);
return true;
}
});
mPlusImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
setValue(mValue + mInterval, true);
}
});
mPlusImageView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue > mValue * 2 ? -1 * Math.floorDiv(-1 * (mMaxValue + mMinValue), 2) : mMaxValue, true);
return true;
}
});
}
protected int getLimitedValue(int v) {
return v < mMinValue ? mMinValue : (v > mMaxValue ? mMaxValue : v);
}
protected int getSeekValue(int v) {
return 0 - Math.floorDiv(mMinValue - v, mInterval);
}
protected String getTextValue(int v) {
if (mDefaultValueTextExists && mDefaultValueExists && v == mDefaultValue) {
return mDefaultValueText;
}
return (mShowSign && v > 0 ? "+" : "") + String.valueOf(v) + mUnits;
}
protected void updateValueViews() {
if (mValueTextView != null) {
if (!mTrackingTouch || mContinuousUpdates) {
if (mDefaultValueTextExists && mDefaultValueExists && mValue == mDefaultValue) {
mValueTextView.setText(mDefaultValueText + " (" +
getContext().getString(R.string.custom_seekbar_default_value) + ")");
} else {
mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, getTextValue(mValue)) +
(mDefaultValueExists && mValue == mDefaultValue ? " (" +
getContext().getString(R.string.custom_seekbar_default_value) + ")" : ""));
}
} else {
if (mDefaultValueTextExists && mDefaultValueExists && mTrackingValue == mDefaultValue) {
mValueTextView.setText("[" + mDefaultValueText + "]");
} else {
mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, "[" + getTextValue(mTrackingValue) + "]"));
}
}
}
if (mResetImageView != null) {
if (!mDefaultValueExists || mValue == mDefaultValue || mTrackingTouch)
mResetImageView.setVisibility(View.INVISIBLE);
else
mResetImageView.setVisibility(View.VISIBLE);
}
if (mMinusImageView != null) {
if (mValue == mMinValue || mTrackingTouch) {
mMinusImageView.setClickable(false);
mMinusImageView.setColorFilter(getContext().getColor(R.color.disabled_text_color),
PorterDuff.Mode.MULTIPLY);
} else {
mMinusImageView.setClickable(true);
mMinusImageView.clearColorFilter();
}
}
if (mPlusImageView != null) {
if (mValue == mMaxValue || mTrackingTouch) {
mPlusImageView.setClickable(false);
mPlusImageView.setColorFilter(getContext().getColor(R.color.disabled_text_color), PorterDuff.Mode.MULTIPLY);
} else {
mPlusImageView.setClickable(true);
mPlusImageView.clearColorFilter();
}
}
}
protected void changeValue(int newValue) {
// for subclasses
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int newValue = getLimitedValue(mMinValue + (progress * mInterval));
if (mTrackingTouch && !mContinuousUpdates) {
mTrackingValue = newValue;
updateValueViews();
} else if (mValue != newValue) {
// change rejected, revert to the previous value
if (!callChangeListener(newValue)) {
mSeekBar.setProgress(getSeekValue(mValue));
return;
}
// change accepted, store it
changeValue(newValue);
persistInt(newValue);
mValue = newValue;
updateValueViews();
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mTrackingValue = mValue;
mTrackingTouch = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mTrackingTouch = false;
if (!mContinuousUpdates)
onProgressChanged(mSeekBar, getSeekValue(mTrackingValue), false);
notifyChanged();
}
@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
if (restoreValue)
mValue = getPersistedInt(mValue);
}
@Override
public void setDefaultValue(Object defaultValue) {
if (defaultValue instanceof Integer)
setDefaultValue((Integer) defaultValue, mSeekBar != null);
else
setDefaultValue(defaultValue == null ? (String) null : defaultValue.toString(), mSeekBar != null);
}
public void setDefaultValue(int newValue, boolean update) {
newValue = getLimitedValue(newValue);
if (!mDefaultValueExists || mDefaultValue != newValue) {
mDefaultValueExists = true;
mDefaultValue = newValue;
if (update)
updateValueViews();
}
}
public void setDefaultValue(String newValue, boolean update) {
if (mDefaultValueExists && (newValue == null || newValue.isEmpty())) {
mDefaultValueExists = false;
if (update)
updateValueViews();
} else if (newValue != null && !newValue.isEmpty()) {
setDefaultValue(Integer.parseInt(newValue), update);
}
}
public void setValue(int newValue) {
mValue = getLimitedValue(newValue);
if (mSeekBar != null) mSeekBar.setProgress(getSeekValue(mValue));
}
public void setValue(int newValue, boolean update) {
newValue = getLimitedValue(newValue);
if (mValue != newValue) {
if (update)
mSeekBar.setProgress(getSeekValue(newValue));
else
mValue = newValue;
}
}
public int getValue() {
return mValue;
}
public void refresh(int newValue) {
// this will ...
setValue(newValue, mSeekBar != null);
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2024 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.settings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import androidx.preference.PreferenceManager;
import org.lineageos.settings.Constants;
import org.lineageos.settings.autohbm.AutoHbmActivity;
import org.lineageos.settings.autohbm.AutoHbmFragment;
import org.lineageos.settings.autohbm.AutoHbmTileService;
import org.lineageos.settings.saturation.SaturationFragment;
import org.lineageos.settings.utils.ComponentUtils;
import org.lineageos.settings.utils.FileUtils;
public class Startup extends BroadcastReceiver {
private static final String TAG = "Startup";
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
Log.d(TAG, "onReceive called with action: " + action);
if (Intent.ACTION_BOOT_COMPLETED.equals(action) ||
Intent.ACTION_REBOOT.equals(action)) {
// Adding a delay before applying the settings
new Handler(Looper.getMainLooper()).postDelayed(() -> {
Log.d(TAG, "Applying saved settings...");
applySavedSaturation(context);
applyAutoHbmSettings(context);
}, 5000); // Delay of 5 seconds
}
}
private void applySavedSaturation(Context context) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
int seekBarValue = sharedPrefs.getInt(Constants.KEY_SATURATION, 100);
Log.d(TAG, "Retrieved seekBarValue: " + seekBarValue);
// Apply the saved saturation value
applySaturation(seekBarValue);
}
private void applySaturation(int seekBarValue) {
Log.d(TAG, "Applying saturation: " + seekBarValue);
float saturation;
if (seekBarValue == 100) {
saturation = 1.001f;
} else {
saturation = seekBarValue / 100.0f;
}
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
if (surfaceFlinger != null) {
try {
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
data.writeFloat(saturation);
surfaceFlinger.transact(1022, data, null, 0);
data.recycle();
Log.d(TAG, "Saturation applied successfully");
} catch (RemoteException e) {
Log.e(TAG, "Failed to apply saturation", e);
}
} else {
Log.e(TAG, "SurfaceFlinger service not found");
}
}
private void applyAutoHbmSettings(Context context) {
Log.d(TAG, "Applying Auto HBM settings...");
AutoHbmFragment.toggleAutoHbmService(context);
ComponentUtils.toggleComponent(
context,
AutoHbmActivity.class,
true
);
ComponentUtils.toggleComponent(
context,
AutoHbmTileService.class,
true
);
Log.d(TAG, "Auto HBM settings applied");
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2025 kenway215
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lineageos.settings;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.service.quicksettings.TileService;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import org.lineageos.settings.gamebar.GameBarSettingsActivity;
import org.lineageos.settings.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);
}
@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,35 @@
/*
* 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.settings.autohbm;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
public class AutoHbmActivity extends CollapsingToolbarBaseActivity {
private static final String TAG = "AutoHbm";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportFragmentManager().beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new AutoHbmFragment(), TAG).commit();
}
}

View File

@@ -0,0 +1,210 @@
/*
* 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.settings.autohbm;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import com.android.settingslib.widget.MainSwitchPreference;
import com.android.settingslib.widget.UsageProgressBarPreference;
import org.lineageos.settings.Constants;
import org.lineageos.settings.CustomSeekBarPreference;
import org.lineageos.settings.R;
import org.lineageos.settings.utils.TileUtils;
public class AutoHbmFragment extends PreferenceFragmentCompat
implements OnCheckedChangeListener, SensorEventListener, Preference.OnPreferenceChangeListener {
private static final String[] AUTO_HBM_PREFERENCES = {
Constants.KEY_AUTO_HBM_THRESHOLD,
Constants.KEY_AUTO_HBM_ENABLE_TIME,
Constants.KEY_AUTO_HBM_DISABLE_TIME,
Constants.KEY_CURRENT_LUX_LEVEL
};
private CustomSeekBarPreference mAutoHbmThresholdPreference;
private MainSwitchPreference mAutoHbmSwitch;
private SensorManager mSensorManager;
private Sensor mLightSensor;
private UsageProgressBarPreference mCurrentLuxLevelPreference;
private int mCurrentLux;
private static boolean mAutoHbmServiceEnabled = false;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.auto_hbm, rootKey);
setHasOptionsMenu(true);
Context context = getContext();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
mAutoHbmSwitch = findPreference(Constants.KEY_AUTO_HBM);
mAutoHbmSwitch.setChecked(sharedPrefs.getBoolean(Constants.KEY_AUTO_HBM, false));
mAutoHbmSwitch.addOnSwitchChangeListener(this);
mAutoHbmThresholdPreference = findPreference(Constants.KEY_AUTO_HBM_THRESHOLD);
mAutoHbmThresholdPreference.setOnPreferenceChangeListener(this);
mCurrentLuxLevelPreference = findPreference(Constants.KEY_CURRENT_LUX_LEVEL);
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
toggleAutoHbmPreferencesVisibility(mAutoHbmSwitch.isChecked());
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.auto_hbm_menu, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.add_tile) {
TileUtils.requestAddTileService(
getContext(),
AutoHbmTileService.class,
R.string.auto_hbm_title,
R.drawable.ic_auto_hbm_tile
);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
public void onResume() {
super.onResume();
if (mAutoHbmSwitch.isChecked()) {
mSensorManager.registerListener(this, mLightSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
@Override
public void onPause() {
super.onPause();
if (mAutoHbmSwitch.isChecked()) {
mSensorManager.unregisterListener(this);
}
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
sharedPrefs.edit().putBoolean(Constants.KEY_AUTO_HBM, isChecked).apply();
toggleAutoHbmService(getContext());
toggleAutoHbmPreferencesVisibility(isChecked);
if (isChecked) {
mSensorManager.registerListener(this, mLightSensor, SensorManager.SENSOR_DELAY_NORMAL);
} else {
mSensorManager.unregisterListener(this);
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mAutoHbmThresholdPreference && mCurrentLuxLevelPreference != null) {
int threshold = (int) newValue;
updateCurrentLuxLevelPreference(mCurrentLux, threshold);
return true;
}
return false;
}
@Override
public void onSensorChanged(SensorEvent event) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
if (event.sensor.getType() == Sensor.TYPE_LIGHT && mCurrentLuxLevelPreference != null) {
float luxValue = event.values[0];
mCurrentLux = (int) luxValue;
int threshold = sharedPrefs.getInt(Constants.KEY_AUTO_HBM_THRESHOLD, 20000);
updateCurrentLuxLevelPreference(mCurrentLux, threshold);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do nothing
}
private void updateCurrentLuxLevelPreference(int currentLux, int threshold) {
if (mCurrentLuxLevelPreference != null) {
mCurrentLuxLevelPreference.setUsageSummary(String.valueOf(currentLux));
mCurrentLuxLevelPreference.setTotalSummary(String.valueOf(threshold));
if (currentLux >= threshold) {
mCurrentLuxLevelPreference.setPercent(100, 100);
} else {
mCurrentLuxLevelPreference.setPercent(currentLux, threshold);
}
}
}
private void toggleAutoHbmPreferencesVisibility(boolean show) {
for (String prefKey : AUTO_HBM_PREFERENCES) {
Preference pref = findPreference(prefKey);
if (pref != null) {
pref.setVisible(show);
}
}
}
public static void toggleAutoHbmService(Context context) {
boolean isAutoHbmEnabled = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(Constants.KEY_AUTO_HBM, false);
if (isAutoHbmEnabled && !mAutoHbmServiceEnabled) {
startAutoHbmService(context);
} else if (!isAutoHbmEnabled && mAutoHbmServiceEnabled) {
stopAutoHbmService(context);
}
}
private static void startAutoHbmService(Context context) {
context.startServiceAsUser(new Intent(context, AutoHbmService.class),
UserHandle.CURRENT);
mAutoHbmServiceEnabled = true;
}
private static void stopAutoHbmService(Context context) {
mAutoHbmServiceEnabled = false;
context.stopServiceAsUser(new Intent(context, AutoHbmService.class),
UserHandle.CURRENT);
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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.settings.autohbm;
import android.app.KeyguardManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.IBinder;
import android.os.PowerManager;
import androidx.preference.PreferenceManager;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.lineageos.settings.Constants;
import org.lineageos.settings.utils.FileUtils;
public class AutoHbmService extends Service {
private static final int MAX_BRIGHTNESS = 4000;
private static final int FALLBACK_BRIGHTNESS = 200;
private static boolean mAutoHbmActive = false;
private ExecutorService mExecutorService;
private SensorManager mSensorManager;
private Sensor mLightSensor;
private SharedPreferences mSharedPrefs;
private int mLastManualBrightness = FALLBACK_BRIGHTNESS;
private boolean mIsAutoBrightnessEnabled = false;
public void activateLightSensorRead() {
submit(() -> {
mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
mSensorManager.registerListener(mSensorEventListener, mLightSensor, SensorManager.SENSOR_DELAY_NORMAL);
});
}
public void deactivateLightSensorRead() {
submit(() -> {
mSensorManager.unregisterListener(mSensorEventListener);
mAutoHbmActive = false;
restoreBrightness();
});
}
private void setBrightnessDirectly(int brightness) {
FileUtils.writeValue(Constants.NODE_BRIGHTNESS, String.valueOf(brightness));
}
private void restoreBrightness() {
if (mIsAutoBrightnessEnabled) {
// Auto-brightness will handle the adjustment
} else {
setBrightnessDirectly(mLastManualBrightness > 0 ? mLastManualBrightness : FALLBACK_BRIGHTNESS);
}
}
private boolean isCurrentlyEnabled() {
String fileValue = FileUtils.getFileValue(Constants.NODE_BRIGHTNESS, "0");
return Integer.parseInt(fileValue) == MAX_BRIGHTNESS;
}
SensorEventListener mSensorEventListener = new SensorEventListener() {
private boolean mCrossedThreshold = false;
private long mCrossedThresholdTime = 0;
private long mLastTriggerTime = 0;
@Override
public void onSensorChanged(SensorEvent event) {
float lux = event.values[0];
KeyguardManager km = (KeyguardManager) getSystemService(getApplicationContext().KEYGUARD_SERVICE);
boolean keyguardShowing = km.inKeyguardRestrictedInputMode();
int luxThreshold = mSharedPrefs.getInt(Constants.KEY_AUTO_HBM_THRESHOLD, 20000);
int timeToEnableHbm = mSharedPrefs.getInt(Constants.KEY_AUTO_HBM_ENABLE_TIME, 0);
int timeToDisableHbm = mSharedPrefs.getInt(Constants.KEY_AUTO_HBM_DISABLE_TIME, 1);
if (lux > luxThreshold) {
if (!mCrossedThreshold) {
mCrossedThreshold = true;
mCrossedThresholdTime = System.currentTimeMillis();
} else {
long currentTime = System.currentTimeMillis();
if (currentTime - mCrossedThresholdTime >= timeToEnableHbm * 1000 && (!mAutoHbmActive || !isCurrentlyEnabled()) && !keyguardShowing) {
mAutoHbmActive = true;
saveCurrentBrightness();
setBrightnessDirectly(MAX_BRIGHTNESS);
mLastTriggerTime = currentTime;
}
}
} else {
mCrossedThreshold = false;
if (mAutoHbmActive) {
long currentTime = System.currentTimeMillis();
if (currentTime - mLastTriggerTime >= timeToDisableHbm * 1000) {
mAutoHbmActive = false;
restoreBrightness();
}
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// do nothing
}
};
private void saveCurrentBrightness() {
try {
String fileValue = FileUtils.getFileValue(Constants.NODE_BRIGHTNESS, String.valueOf(FALLBACK_BRIGHTNESS));
mLastManualBrightness = Integer.parseInt(fileValue);
} catch (NumberFormatException e) {
mLastManualBrightness = FALLBACK_BRIGHTNESS;
}
mIsAutoBrightnessEnabled = mSharedPrefs.getBoolean("auto_brightness", false);
}
private BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
activateLightSensorRead();
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
deactivateLightSensorRead();
}
}
};
@Override
public void onCreate() {
mExecutorService = Executors.newSingleThreadExecutor();
IntentFilter screenStateFilter = new IntentFilter(Intent.ACTION_SCREEN_ON);
screenStateFilter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mScreenStateReceiver, screenStateFilter);
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm.isInteractive()) {
activateLightSensorRead();
}
}
private Future<?> submit(Runnable runnable) {
return mExecutorService.submit(runnable);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mScreenStateReceiver);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm.isInteractive()) {
deactivateLightSensorRead();
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.settings.autohbm;
import android.content.SharedPreferences;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import androidx.preference.PreferenceManager;
import org.lineageos.settings.Constants;
import org.lineageos.settings.R;
import org.lineageos.settings.utils.FileUtils;
public class AutoHbmTileService extends TileService {
@Override
public void onStartListening() {
super.onStartListening();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
updateTile(sharedPrefs.getBoolean(Constants.KEY_AUTO_HBM, false));
}
@Override
public void onStopListening() {
super.onStopListening();
}
@Override
public void onClick() {
super.onClick();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
final boolean enabled = !(sharedPrefs.getBoolean(Constants.KEY_AUTO_HBM, false));
sharedPrefs.edit().putBoolean(Constants.KEY_AUTO_HBM, enabled).commit();
AutoHbmFragment.toggleAutoHbmService(this);
updateTile(enabled);
}
private void updateTile(boolean enabled) {
final Tile tile = getQsTile();
tile.setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
// Set the title for the tile using the new string resource
String title = getString(R.string.tile_auto_hbm);
tile.setLabel(title);
// Set the subtitle based on the state
String subtitle = enabled ? getString(R.string.tile_hbm_on) : getString(R.string.tile_hbm_off);
tile.setSubtitle(subtitle);
tile.updateTile();
}
}

View File

@@ -0,0 +1,158 @@
/*
* 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.settings.autohbm;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import androidx.preference.PreferenceManager;
import org.lineageos.settings.Constants;
import org.lineageos.settings.R;
import org.lineageos.settings.utils.FileUtils;
public class HbmTileService extends TileService {
private static final int MAX_BRIGHTNESS = 4000;
private static final int FALLBACK_BRIGHTNESS = 200;
private static final String NOTIFICATION_CHANNEL_ID = "hbm_tile_service_channel";
private static final int NOTIFICATION_ID = 2;
private int mLastManualBrightness = FALLBACK_BRIGHTNESS;
private boolean mIsAutoBrightnessEnabled = false;
private NotificationManager mNotificationManager;
@Override
public void onCreate() {
super.onCreate();
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
setupNotificationChannel();
}
@Override
public void onStartListening() {
super.onStartListening();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean isAutoHbmEnabled = sharedPrefs.getBoolean(Constants.KEY_AUTO_HBM, false);
mIsAutoBrightnessEnabled = isSystemAutoBrightnessEnabled();
boolean isHbmEnabled = isCurrentlyEnabled();
updateTile(isAutoHbmEnabled, mIsAutoBrightnessEnabled, isHbmEnabled);
}
@Override
public void onClick() {
super.onClick();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean isAutoHbmEnabled = sharedPrefs.getBoolean(Constants.KEY_AUTO_HBM, false);
mIsAutoBrightnessEnabled = isSystemAutoBrightnessEnabled();
if (isAutoHbmEnabled || mIsAutoBrightnessEnabled) {
// Do nothing if Auto HBM or Auto Brightness is enabled
return;
}
boolean isHbmEnabled = isCurrentlyEnabled();
if (isHbmEnabled) {
restoreBrightness();
cancelHbmNotification();
} else {
saveCurrentBrightness();
setBrightnessDirectly(MAX_BRIGHTNESS);
showHbmNotification();
}
updateTile(isAutoHbmEnabled, mIsAutoBrightnessEnabled, !isHbmEnabled);
}
private void updateTile(boolean isAutoHbmEnabled, boolean isAutoBrightnessEnabled, boolean isHbmEnabled) {
final Tile tile = getQsTile();
if (isAutoHbmEnabled || isAutoBrightnessEnabled) {
tile.setState(Tile.STATE_UNAVAILABLE);
tile.setLabel(getString(R.string.tile_auto_hbm_or_brightness_enabled));
tile.setSubtitle(null);
} else {
tile.setState(isHbmEnabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
tile.setLabel(getString(R.string.tile_hbm));
tile.setSubtitle(isHbmEnabled ? getString(R.string.tile_hbm_on) : getString(R.string.tile_hbm_off));
}
tile.updateTile();
}
private void setBrightnessDirectly(int brightness) {
FileUtils.writeValue(Constants.NODE_BRIGHTNESS, String.valueOf(brightness));
}
private void restoreBrightness() {
if (mIsAutoBrightnessEnabled) {
return;
}
setBrightnessDirectly(mLastManualBrightness > 0 ? mLastManualBrightness : FALLBACK_BRIGHTNESS);
}
private boolean isCurrentlyEnabled() {
String fileValue = FileUtils.getFileValue(Constants.NODE_BRIGHTNESS, "0");
return Integer.parseInt(fileValue) == MAX_BRIGHTNESS;
}
private void saveCurrentBrightness() {
try {
String fileValue = FileUtils.getFileValue(Constants.NODE_BRIGHTNESS, String.valueOf(FALLBACK_BRIGHTNESS));
mLastManualBrightness = Integer.parseInt(fileValue);
} catch (NumberFormatException e) {
mLastManualBrightness = FALLBACK_BRIGHTNESS;
}
}
private boolean isSystemAutoBrightnessEnabled() {
try {
return Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
} catch (Settings.SettingNotFoundException e) {
return false;
}
}
private void setupNotificationChannel() {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
getString(R.string.hbm_mode_title),
NotificationManager.IMPORTANCE_DEFAULT
);
channel.setBlockable(true);
mNotificationManager.createNotificationChannel(channel);
}
private void showHbmNotification() {
Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle(getString(R.string.hbm_mode_title))
.setContentText(getString(R.string.hbm_mode_notification))
.setSmallIcon(R.drawable.ic_auto_hbm_tile)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setFlag(Notification.FLAG_NO_CLEAR, true)
.build();
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
private void cancelHbmNotification() {
mNotificationManager.cancel(NOTIFICATION_ID);
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2025 kenway214
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lineageos.settings.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.settings.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.settings.R;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class 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.settings.gamebar;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import org.lineageos.settings.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.settings.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.settings.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.settings.gamebar;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import org.lineageos.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.gamebar;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import androidx.preference.PreferenceManager;
import org.lineageos.settings.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.settings.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,33 @@
/*
* Copyright (C) 2020-2022 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lineageos.settings.refreshrate;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
public class RefreshActivity extends CollapsingToolbarBaseActivity {
private static final String TAG_REFRESH = "refresh";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new RefreshSettingsFragment(), TAG_REFRESH).commit();
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 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.settings.refreshrate;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.os.RemoteException;
public class RefreshService extends Service {
private static final String TAG = "RefreshService";
private static final boolean DEBUG = true;
private String mPreviousApp;
private RefreshUtils mRefreshUtils;
private IActivityTaskManager mActivityTaskManager;
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mPreviousApp = "";
}
};
@Override
public void onCreate() {
if (DEBUG) Log.d(TAG, "Creating service");
try {
mActivityTaskManager = ActivityTaskManager.getService();
mActivityTaskManager.registerTaskStackListener(mTaskListener);
} catch (RemoteException e) {
// Do nothing
}
mRefreshUtils = new RefreshUtils(this);
registerReceiver();
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) Log.d(TAG, "Starting service");
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void registerReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
// filter.addAction(Intent.ACTION_SCREEN_ON);
this.registerReceiver(mIntentReceiver, filter);
}
private final TaskStackListener mTaskListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
try {
final RootTaskInfo info = mActivityTaskManager.getFocusedRootTaskInfo();
if (info == null || info.topActivity == null) {
return;
}
String foregroundApp = info.topActivity.getPackageName();
if (!mRefreshUtils.isAppInList) {
mRefreshUtils.getOldRate();
}
if (!foregroundApp.equals(mPreviousApp)) {
mRefreshUtils.setRefreshRate(foregroundApp);
mPreviousApp = foregroundApp;
}
} catch (Exception e) {}
}
};
}

View File

@@ -0,0 +1,422 @@
/**
* Copyright (C) 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.settings.refreshrate;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceFragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.android.settingslib.applications.ApplicationsState;
import org.lineageos.settings.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RefreshSettingsFragment extends PreferenceFragment
implements ApplicationsState.Callbacks {
private AllPackagesAdapter mAllPackagesAdapter;
private ApplicationsState mApplicationsState;
private ApplicationsState.Session mSession;
private ActivityFilter mActivityFilter;
private Map<String, ApplicationsState.AppEntry> mEntryMap =
new HashMap<String, ApplicationsState.AppEntry>();
private RefreshUtils mRefreshUtils;
private RecyclerView mAppsRecyclerView;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
mSession = mApplicationsState.newSession(this);
mSession.onResume();
mActivityFilter = new ActivityFilter(getActivity().getPackageManager());
mAllPackagesAdapter = new AllPackagesAdapter(getActivity());
mRefreshUtils = new RefreshUtils(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.refresh_layout, container, false);
}
@Override
public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mAppsRecyclerView = view.findViewById(R.id.refresh_rv_view);
mAppsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mAppsRecyclerView.setAdapter(mAllPackagesAdapter);
}
@Override
public void onResume() {
super.onResume();
getActivity().setTitle(getResources().getString(R.string.refresh_title));
rebuild();
}
@Override
public void onDestroy() {
super.onDestroy();
mSession.onPause();
mSession.onDestroy();
}
@Override
public void onPackageListChanged() {
mActivityFilter.updateLauncherInfoList();
rebuild();
}
@Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> entries) {
if (entries != null) {
handleAppEntries(entries);
mAllPackagesAdapter.notifyDataSetChanged();
}
}
@Override
public void onLoadEntriesCompleted() {
rebuild();
}
@Override
public void onAllSizesComputed() {
}
@Override
public void onLauncherInfoChanged() {
}
@Override
public void onPackageIconChanged() {
}
@Override
public void onPackageSizeChanged(String packageName) {
}
@Override
public void onRunningStateChanged(boolean running) {
}
private void handleAppEntries(List<ApplicationsState.AppEntry> entries) {
final ArrayList<String> sections = new ArrayList<String>();
final ArrayList<Integer> positions = new ArrayList<Integer>();
final PackageManager pm = getActivity().getPackageManager();
String lastSectionIndex = null;
int offset = 0;
for (int i = 0; i < entries.size(); i++) {
final ApplicationInfo info = entries.get(i).info;
final String label = (String) info.loadLabel(pm);
final String sectionIndex;
if (!info.enabled) {
sectionIndex = "--"; // XXX
} else if (TextUtils.isEmpty(label)) {
sectionIndex = "";
} else {
sectionIndex = label.substring(0, 1).toUpperCase();
}
if (lastSectionIndex == null ||
!TextUtils.equals(sectionIndex, lastSectionIndex)) {
sections.add(sectionIndex);
positions.add(offset);
lastSectionIndex = sectionIndex;
}
offset++;
}
mAllPackagesAdapter.setEntries(entries, sections, positions);
mEntryMap.clear();
for (ApplicationsState.AppEntry e : entries) {
mEntryMap.put(e.info.packageName, e);
}
}
private void rebuild() {
mSession.rebuild(mActivityFilter, ApplicationsState.ALPHA_COMPARATOR);
}
private int getStateDrawable(int state) {
switch (state) {
case RefreshUtils.STATE_STANDARD:
return R.drawable.ic_refresh_60;
case RefreshUtils.STATE_HIGH:
return R.drawable.ic_refresh_90;
case RefreshUtils.STATE_EXTREME:
return R.drawable.ic_refresh_120;
case RefreshUtils.STATE_DEFAULT:
default:
return R.drawable.ic_refresh_default;
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
private TextView title;
private Spinner mode;
private ImageView icon;
private View rootView;
private ImageView stateIcon;
private ViewHolder(View view) {
super(view);
this.title = view.findViewById(R.id.app_name);
this.mode = view.findViewById(R.id.app_mode);
this.icon = view.findViewById(R.id.app_icon);
this.stateIcon = view.findViewById(R.id.state);
this.rootView = view;
view.setTag(this);
}
}
private class ModeAdapter extends BaseAdapter {
private final LayoutInflater inflater;
private final int[] items = {
R.string.refresh_default,
R.string.refresh_standard,
R.string.refresh_high,
R.string.refresh_extreme
};
private ModeAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return items.length;
}
@Override
public Object getItem(int position) {
return items[position];
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView view;
if (convertView != null) {
view = (TextView) convertView;
} else {
view = (TextView) inflater.inflate(android.R.layout.simple_spinner_dropdown_item,
parent, false);
}
view.setText(items[position]);
view.setTextSize(14f);
return view;
}
}
private class AllPackagesAdapter extends RecyclerView.Adapter<ViewHolder>
implements AdapterView.OnItemSelectedListener, SectionIndexer {
private List<ApplicationsState.AppEntry> mEntries = new ArrayList<>();
private String[] mSections;
private int[] mPositions;
public AllPackagesAdapter(Context context) {
mActivityFilter = new ActivityFilter(context.getPackageManager());
}
@Override
public int getItemCount() {
return mEntries.size();
}
@Override
public long getItemId(int position) {
return mEntries.get(position).id;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.refresh_list_item, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Context context = holder.itemView.getContext();
ApplicationsState.AppEntry entry = mEntries.get(position);
if (entry == null) {
return;
}
holder.mode.setAdapter(new ModeAdapter(context));
holder.mode.setOnItemSelectedListener(this);
holder.title.setText(entry.label);
holder.title.setOnClickListener(v -> holder.mode.performClick());
mApplicationsState.ensureIcon(entry);
holder.icon.setImageDrawable(entry.icon);
int packageState = mRefreshUtils.getStateForPackage(entry.info.packageName);
holder.mode.setSelection(packageState, false);
holder.mode.setTag(entry);
holder.stateIcon.setImageResource(getStateDrawable(packageState));
}
private void setEntries(List<ApplicationsState.AppEntry> entries,
List<String> sections, List<Integer> positions) {
mEntries = entries;
mSections = sections.toArray(new String[sections.size()]);
mPositions = new int[positions.size()];
for (int i = 0; i < positions.size(); i++) {
mPositions[i] = positions.get(i);
}
notifyDataSetChanged();
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
final ApplicationsState.AppEntry entry = (ApplicationsState.AppEntry) parent.getTag();
int currentState = mRefreshUtils.getStateForPackage(entry.info.packageName);
if (currentState != position) {
mRefreshUtils.writePackage(entry.info.packageName, position);
notifyDataSetChanged();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
@Override
public int getPositionForSection(int section) {
if (section < 0 || section >= mSections.length) {
return -1;
}
return mPositions[section];
}
@Override
public int getSectionForPosition(int position) {
if (position < 0 || position >= getItemCount()) {
return -1;
}
final int index = Arrays.binarySearch(mPositions, position);
/*
* Consider this example: section positions are 0, 3, 5; the supplied
* position is 4. The section corresponding to position 4 starts at
* position 3, so the expected return value is 1. Binary search will not
* find 4 in the array and thus will return -insertPosition-1, i.e. -3.
* To get from that number to the expected value of 1 we need to negate
* and subtract 2.
*/
return index >= 0 ? index : -index - 2;
}
@Override
public Object[] getSections() {
return mSections;
}
}
private class ActivityFilter implements ApplicationsState.AppFilter {
private final PackageManager mPackageManager;
private final List<String> mLauncherResolveInfoList = new ArrayList<String>();
private ActivityFilter(PackageManager packageManager) {
this.mPackageManager = packageManager;
updateLauncherInfoList();
}
public void updateLauncherInfoList() {
Intent i = new Intent(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfoList = mPackageManager.queryIntentActivities(i, 0);
synchronized (mLauncherResolveInfoList) {
mLauncherResolveInfoList.clear();
for (ResolveInfo ri : resolveInfoList) {
mLauncherResolveInfoList.add(ri.activityInfo.packageName);
}
}
}
@Override
public void init() {
}
@Override
public boolean filterApp(ApplicationsState.AppEntry entry) {
boolean show = !mAllPackagesAdapter.mEntries.contains(entry.info.packageName);
if (show) {
synchronized (mLauncherResolveInfoList) {
show = mLauncherResolveInfoList.contains(entry.info.packageName);
}
}
return show;
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (C) 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.settings.refreshrate;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.UserHandle;
import android.view.Display;
import android.provider.Settings;
import androidx.preference.PreferenceManager;
public final class RefreshUtils {
private static final String REFRESH_CONTROL = "refresh_control";
private static float defaultMaxRate;
private static float defaultMinRate;
private static final String KEY_PEAK_REFRESH_RATE = "peak_refresh_rate";
private static final String KEY_MIN_REFRESH_RATE = "min_refresh_rate";
private Context mContext;
protected static boolean isAppInList = false;
protected static final int STATE_DEFAULT = 0;
protected static final int STATE_STANDARD = 1;
protected static final int STATE_HIGH = 2;
protected static final int STATE_EXTREME = 3;
private static final float REFRESH_STATE_DEFAULT = 120f;
private static final float REFRESH_STATE_STANDARD = 60f;
private static final float REFRESH_STATE_HIGH = 90f;
private static final float REFRESH_STATE_EXTREME = 120f;
private static final String REFRESH_STANDARD = "refresh.standard=";
private static final String REFRESH_HIGH = "refresh.high=";
private static final String REFRESH_EXTREME = "refresh.extreme=";
private SharedPreferences mSharedPrefs;
protected RefreshUtils(Context context) {
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
mContext = context;
}
public static void startService(Context context) {
context.startServiceAsUser(new Intent(context, RefreshService.class),
UserHandle.CURRENT);
}
private void writeValue(String profiles) {
mSharedPrefs.edit().putString(REFRESH_CONTROL, profiles).apply();
}
protected void getOldRate(){
defaultMaxRate = Settings.System.getFloat(mContext.getContentResolver(), KEY_PEAK_REFRESH_RATE, REFRESH_STATE_DEFAULT);
defaultMinRate = Settings.System.getFloat(mContext.getContentResolver(), KEY_MIN_REFRESH_RATE, REFRESH_STATE_DEFAULT);
}
private String getValue() {
String value = mSharedPrefs.getString(REFRESH_CONTROL, null);
if (value == null || value.isEmpty()) {
value = REFRESH_STANDARD + ":" + REFRESH_HIGH + ":" + REFRESH_EXTREME;
writeValue(value);
}
return value;
}
protected void writePackage(String packageName, int mode) {
String value = getValue();
value = value.replace(packageName + ",", "");
String[] modes = value.split(":");
String finalString;
switch (mode) {
case STATE_STANDARD:
modes[0] = modes[0] + packageName + ",";
break;
case STATE_HIGH:
modes[1] = modes[1] + packageName + ",";
break;
case STATE_EXTREME:
modes[2] = modes[2] + packageName + ",";
break;
}
finalString = modes[0] + ":" + modes[1] + ":" + modes[2];
writeValue(finalString);
}
protected int getStateForPackage(String packageName) {
String value = getValue();
String[] modes = value.split(":");
int state = STATE_DEFAULT;
if (modes[0].contains(packageName + ",")) {
state = STATE_STANDARD;
} else if (modes[1].contains(packageName + ",")) {
state = STATE_HIGH;
} else if (modes[2].contains(packageName + ",")) {
state = STATE_EXTREME;
}
return state;
}
protected void setRefreshRate(String packageName) {
String value = getValue();
String modes[];
float maxrate = defaultMaxRate;
float minrate = defaultMinRate;
isAppInList = false;
if (value != null) {
modes = value.split(":");
if (modes[0].contains(packageName + ",")) {
maxrate = REFRESH_STATE_STANDARD;
if ( minrate > maxrate){
minrate = maxrate;
}
isAppInList = true;
} else if (modes[1].contains(packageName + ",")) {
maxrate = REFRESH_STATE_HIGH;
if (minrate > maxrate) {
minrate = maxrate;
}
isAppInList = true;
} else if (modes[2].contains(packageName + ",")) {
maxrate = REFRESH_STATE_EXTREME;
if ( minrate > maxrate){
minrate = maxrate;
}
isAppInList = true;
}
}
Settings.System.putFloat(mContext.getContentResolver(), KEY_MIN_REFRESH_RATE, minrate);
Settings.System.putFloat(mContext.getContentResolver(), KEY_PEAK_REFRESH_RATE, maxrate);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 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.settings.saturation;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
public class SaturationActivity extends CollapsingToolbarBaseActivity {
private static final String TAG = "Saturation";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportFragmentManager().beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new SaturationFragment(), TAG).commit();
}
}

View File

@@ -0,0 +1,267 @@
/*
* Copyright (C) 2024 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.settings.saturation;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.android.settingslib.widget.LayoutPreference;
import org.lineageos.settings.Constants;
import org.lineageos.settings.CustomSeekBarPreference;
import org.lineageos.settings.R;
import org.lineageos.settings.utils.TileUtils;
import java.util.Arrays;
public class SaturationFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceChangeListener {
private View mViewArrowPrevious;
private View mViewArrowNext;
private ViewPager mViewPager;
private ImageView[] mDotIndicators;
private View[] mViewPagerImages;
private CustomSeekBarPreference mSaturationPreference;
private IBinder mSurfaceFlinger;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.saturation, rootKey);
setHasOptionsMenu(true);
LayoutPreference preview = findPreference(Constants.KEY_SATURATION_PREVIEW);
addViewPager(preview);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
mSaturationPreference = (CustomSeekBarPreference) findPreference(Constants.KEY_SATURATION);
mSaturationPreference.setOnPreferenceChangeListener(this);
int seekBarValue = sharedPrefs.getInt(Constants.KEY_SATURATION, 100);
updateSaturation(seekBarValue);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.saturation_menu, menu);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mSaturationPreference) {
int seekBarValue = (Integer) newValue;
updateSaturation(seekBarValue);
return true;
}
return false;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.add_tile) {
TileUtils.requestAddTileService(
getContext(),
SaturationTileService.class,
R.string.saturation_title,
R.drawable.ic_saturation_tile
);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
public void updateSaturation(int seekBarValue) {
float saturation;
if (seekBarValue == 100) {
saturation = 1.001f;
} else {
saturation = seekBarValue / 100.0f;
}
if (mSurfaceFlinger != null) {
try {
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
data.writeFloat(saturation);
mSurfaceFlinger.transact(1022, data, null, 0);
data.recycle();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger");
}
public void restoreSaturationSetting(Context context) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
int seekBarValue = sharedPrefs.getInt(Constants.KEY_SATURATION, 100);
updateSaturation(seekBarValue);
}
void addViewPager(LayoutPreference preview) {
mViewPager = preview.findViewById(R.id.viewpager);
int[] drawables = new int[]{
R.drawable.image_preview1,
R.drawable.image_preview2,
R.drawable.image_preview3
};
mViewPagerImages = new View[drawables.length];
for (int idx = 0; idx < drawables.length; idx++) {
mViewPagerImages[idx] = getLayoutInflater().inflate(R.layout.image_layout, null);
ImageView imageView = mViewPagerImages[idx].findViewById(R.id.imageView);
imageView.setImageResource(drawables[idx]);
}
mViewPager.setAdapter(new ImagePreviewPagerAdapter(mViewPagerImages));
mViewArrowPrevious = preview.findViewById(R.id.arrow_previous);
mViewArrowPrevious.setOnClickListener(v -> mViewPager.setCurrentItem(mViewPager.getCurrentItem() - 1, true));
mViewArrowNext = preview.findViewById(R.id.arrow_next);
mViewArrowNext.setOnClickListener(v -> mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1, true));
mViewPager.addOnPageChangeListener(createPageListener());
final ViewGroup viewGroup = preview.findViewById(R.id.viewGroup);
mDotIndicators = new ImageView[mViewPagerImages.length];
for (int i = 0; i < mViewPagerImages.length; i++) {
final ImageView imageView = new ImageView(getContext());
final ViewGroup.MarginLayoutParams lp =
new ViewGroup.MarginLayoutParams(12, 12);
lp.setMargins(6, 0, 6, 0);
imageView.setLayoutParams(lp);
mDotIndicators[i] = imageView;
viewGroup.addView(mDotIndicators[i]);
}
updateIndicator(mViewPager.getCurrentItem());
}
private ViewPager.OnPageChangeListener createPageListener() {
return new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(
int position, float positionOffset, int positionOffsetPixels) {
if (positionOffset != 0) {
for (View mViewPagerImage : mViewPagerImages) {
mViewPagerImage.setVisibility(View.VISIBLE);
}
} else {
mViewPagerImages[position].setContentDescription(
getContext().getString(R.string.image_preview_content_description));
updateIndicator(position);
}
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
};
}
private void updateIndicator(int position) {
for (int i = 0; i < mViewPagerImages.length; i++) {
if (position == i) {
mDotIndicators[i].setBackgroundResource(
R.drawable.ic_image_preview_page_indicator_focused);
mViewPagerImages[i].setVisibility(View.VISIBLE);
} else {
mDotIndicators[i].setBackgroundResource(
R.drawable.ic_image_preview_page_indicator_unfocused);
mViewPagerImages[i].setVisibility(View.INVISIBLE);
}
}
if (position == 0) {
mViewArrowPrevious.setVisibility(View.INVISIBLE);
mViewArrowNext.setVisibility(View.VISIBLE);
} else if (position == (mViewPagerImages.length - 1)) {
mViewArrowPrevious.setVisibility(View.VISIBLE);
mViewArrowNext.setVisibility(View.INVISIBLE);
} else {
mViewArrowPrevious.setVisibility(View.VISIBLE);
mViewArrowNext.setVisibility(View.VISIBLE);
}
}
static class ImagePreviewPagerAdapter extends PagerAdapter {
private final View[] mPageViewList;
ImagePreviewPagerAdapter(View[] pageViewList) {
mPageViewList = pageViewList;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mPageViewList[position] != null) {
container.removeView(mPageViewList[position]);
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(mPageViewList[position]);
return mPageViewList[position];
}
@Override
public int getCount() {
return mPageViewList.length;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return object == view;
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 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.settings.saturation;
import android.app.PendingIntent;
import android.content.Intent;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
public class SaturationTileService extends TileService {
@Override
public void onStartListening() {
super.onStartListening();
updateTile();
}
@Override
public void onClick() {
super.onClick();
Intent intent = new Intent(this, SaturationActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
);
startActivityAndCollapse(pendingIntent);
}
private void updateTile() {
final Tile tile = getQsTile();
tile.setState(Tile.STATE_ACTIVE);
tile.updateTile();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2020 Paranoid Android
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lineageos.settings.speaker;
import android.os.Bundle;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
public class ClearSpeakerActivity extends CollapsingToolbarBaseActivity {
private static final String TAG_CLEARSPEAKER = "clearspeaker";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new ClearSpeakerFragment(), TAG_CLEARSPEAKER).commit();
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2023 Paranoid Android
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lineageos.settings.speaker;
import android.content.res.AssetFileDescriptor;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragment;
import androidx.preference.SwitchPreference;
import org.lineageos.settings.R;
import java.io.IOException;
public class ClearSpeakerFragment extends PreferenceFragment implements
Preference.OnPreferenceChangeListener {
private static final String TAG = "ClearSpeakerFragment";
private static final String PREF_CLEAR_SPEAKER = "clear_speaker_pref";
private static final int PLAY_DURATION_MS = 30000;
private Handler mHandler = new Handler(Looper.getMainLooper());
private MediaPlayer mMediaPlayer;
private SwitchPreference mClearSpeakerPref;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.clear_speaker_settings);
mClearSpeakerPref = findPreference(PREF_CLEAR_SPEAKER);
mClearSpeakerPref.setOnPreferenceChangeListener(this);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mClearSpeakerPref) {
boolean value = (Boolean) newValue;
if (value && startPlaying()) {
mHandler.removeCallbacksAndMessages(null);
mHandler.postDelayed(this::stopPlaying, PLAY_DURATION_MS);
return true;
}
}
return false;
}
@Override
public void onStop() {
super.onStop();
stopPlaying();
}
public boolean startPlaying() {
getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build());
mMediaPlayer.setLooping(true);
try (AssetFileDescriptor afd = getResources().openRawResourceFd(
R.raw.clear_speaker_sound)) {
mMediaPlayer.setDataSource(afd);
mMediaPlayer.setVolume(1.0f, 1.0f);
mMediaPlayer.prepare();
mMediaPlayer.start();
mClearSpeakerPref.setEnabled(false);
} catch (IOException | IllegalArgumentException e) {
Log.e(TAG, "Failed to play speaker clean sound!", e);
return false;
}
return true;
}
public void stopPlaying() {
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
try {
mMediaPlayer.stop();
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to stop media player!", e);
} finally {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer=null;
}
}
mClearSpeakerPref.setEnabled(true);
mClearSpeakerPref.setChecked(false);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 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.settings.thermal;
import android.os.Bundle;
import android.view.MenuItem;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
public class ThermalActivity extends CollapsingToolbarBaseActivity {
private static final String TAG_THERMAL = "thermal";
private static final String THERMAL_SCONFIG = "/sys/class/thermal/thermal_message/sconfig";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new ThermalSettingsFragment(), TAG_THERMAL).commit();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 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.settings.thermal;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
import android.app.Service;
import android.app.TaskStackListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class ThermalService extends Service {
private static final String TAG = "ThermalService";
private static final boolean DEBUG = false;
private boolean mScreenOn = true;
private String mCurrentApp = "";
private ThermalUtils mThermalUtils;
private IActivityTaskManager mActivityTaskManager;
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case Intent.ACTION_SCREEN_OFF:
mScreenOn = false;
setThermalProfile();
break;
case Intent.ACTION_SCREEN_ON:
mScreenOn = true;
setThermalProfile();
break;
}
}
};
@Override
public void onCreate() {
if (DEBUG) Log.d(TAG, "Creating service");
try {
mActivityTaskManager = ActivityTaskManager.getService();
mActivityTaskManager.registerTaskStackListener(mTaskListener);
} catch (RemoteException e) {
// Do nothing
}
mThermalUtils = new ThermalUtils(this);
registerReceiver();
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) Log.d(TAG, "Starting service");
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void registerReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
this.registerReceiver(mIntentReceiver, filter);
}
private void setThermalProfile() {
if (mScreenOn) {
mThermalUtils.setThermalProfile(mCurrentApp);
} else {
mThermalUtils.setDefaultThermalProfile();
}
}
private final TaskStackListener mTaskListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
try {
final ActivityTaskManager.RootTaskInfo focusedTask =
ActivityTaskManager.getService().getFocusedRootTaskInfo();
if (focusedTask != null && focusedTask.topActivity != null) {
ComponentName taskComponentName = focusedTask.topActivity;
String foregroundApp = taskComponentName.getPackageName();
if (!foregroundApp.equals(mCurrentApp)) {
mCurrentApp = foregroundApp;
setThermalProfile();
}
}
} catch (Exception e) {}
}
};
}

View File

@@ -0,0 +1,432 @@
/**
* Copyright (C) 2020 The LineageOS Project
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lineageos.settings.thermal;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceFragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.android.settingslib.applications.ApplicationsState;
import org.lineageos.settings.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ThermalSettingsFragment extends PreferenceFragment
implements ApplicationsState.Callbacks {
private AllPackagesAdapter mAllPackagesAdapter;
private ApplicationsState mApplicationsState;
private ApplicationsState.Session mSession;
private ActivityFilter mActivityFilter;
private Map<String, ApplicationsState.AppEntry> mEntryMap =
new HashMap<String, ApplicationsState.AppEntry>();
private ThermalUtils mThermalUtils;
private RecyclerView mAppsRecyclerView;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
mSession = mApplicationsState.newSession(this);
mSession.onResume();
mActivityFilter = new ActivityFilter(getActivity().getPackageManager());
mAllPackagesAdapter = new AllPackagesAdapter(getActivity());
mThermalUtils = new ThermalUtils(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.thermal_layout, container, false);
}
@Override
public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mAppsRecyclerView = view.findViewById(R.id.thermal_rv_view);
mAppsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mAppsRecyclerView.setAdapter(mAllPackagesAdapter);
}
@Override
public void onResume() {
super.onResume();
getActivity().setTitle(getResources().getString(R.string.thermal_title));
rebuild();
}
@Override
public void onDestroy() {
super.onDestroy();
mSession.onPause();
mSession.onDestroy();
}
@Override
public void onPackageListChanged() {
mActivityFilter.updateLauncherInfoList();
rebuild();
}
@Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> entries) {
if (entries != null) {
handleAppEntries(entries);
mAllPackagesAdapter.notifyDataSetChanged();
}
}
@Override
public void onLoadEntriesCompleted() {
rebuild();
}
@Override
public void onAllSizesComputed() {
}
@Override
public void onLauncherInfoChanged() {
}
@Override
public void onPackageIconChanged() {
}
@Override
public void onPackageSizeChanged(String packageName) {
}
@Override
public void onRunningStateChanged(boolean running) {
}
private void handleAppEntries(List<ApplicationsState.AppEntry> entries) {
final ArrayList<String> sections = new ArrayList<String>();
final ArrayList<Integer> positions = new ArrayList<Integer>();
final PackageManager pm = getActivity().getPackageManager();
String lastSectionIndex = null;
int offset = 0;
for (int i = 0; i < entries.size(); i++) {
final ApplicationInfo info = entries.get(i).info;
final String label = (String) info.loadLabel(pm);
final String sectionIndex;
if (!info.enabled) {
sectionIndex = "--"; // XXX
} else if (TextUtils.isEmpty(label)) {
sectionIndex = "";
} else {
sectionIndex = label.substring(0, 1).toUpperCase();
}
if (lastSectionIndex == null ||
!TextUtils.equals(sectionIndex, lastSectionIndex)) {
sections.add(sectionIndex);
positions.add(offset);
lastSectionIndex = sectionIndex;
}
offset++;
}
mAllPackagesAdapter.setEntries(entries, sections, positions);
mEntryMap.clear();
for (ApplicationsState.AppEntry e : entries) {
mEntryMap.put(e.info.packageName, e);
}
}
private void rebuild() {
mSession.rebuild(mActivityFilter, ApplicationsState.ALPHA_COMPARATOR);
}
private int getStateDrawable(int state) {
switch (state) {
case ThermalUtils.STATE_BENCHMARK:
return R.drawable.ic_thermal_benchmark;
case ThermalUtils.STATE_BROWSER:
return R.drawable.ic_thermal_browser;
case ThermalUtils.STATE_CAMERA:
return R.drawable.ic_thermal_camera;
case ThermalUtils.STATE_DIALER:
return R.drawable.ic_thermal_dialer;
case ThermalUtils.STATE_GAMING:
return R.drawable.ic_thermal_gaming;
case ThermalUtils.STATE_STREAMING:
return R.drawable.ic_thermal_streaming;
case ThermalUtils.STATE_DEFAULT:
default:
return R.drawable.ic_thermal_default;
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
private TextView title;
private Spinner mode;
private ImageView icon;
private View rootView;
private ImageView stateIcon;
private ViewHolder(View view) {
super(view);
this.title = view.findViewById(R.id.app_name);
this.mode = view.findViewById(R.id.app_mode);
this.icon = view.findViewById(R.id.app_icon);
this.stateIcon = view.findViewById(R.id.state);
this.rootView = view;
view.setTag(this);
}
}
private class ModeAdapter extends BaseAdapter {
private final LayoutInflater inflater;
private final int[] items = {
R.string.thermal_default,
R.string.thermal_benchmark,
R.string.thermal_browser,
R.string.thermal_camera,
R.string.thermal_dialer,
R.string.thermal_gaming,
R.string.thermal_streaming
};
private ModeAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return items.length;
}
@Override
public Object getItem(int position) {
return items[position];
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView view;
if (convertView != null) {
view = (TextView) convertView;
} else {
view = (TextView) inflater.inflate(android.R.layout.simple_spinner_dropdown_item,
parent, false);
}
view.setText(items[position]);
view.setTextSize(14f);
return view;
}
}
private class AllPackagesAdapter extends RecyclerView.Adapter<ViewHolder>
implements AdapterView.OnItemSelectedListener, SectionIndexer {
private List<ApplicationsState.AppEntry> mEntries = new ArrayList<>();
private String[] mSections;
private int[] mPositions;
public AllPackagesAdapter(Context context) {
mActivityFilter = new ActivityFilter(context.getPackageManager());
}
@Override
public int getItemCount() {
return mEntries.size();
}
@Override
public long getItemId(int position) {
return mEntries.get(position).id;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ViewHolder holder = new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.thermal_list_item, parent, false));
Context context = holder.itemView.getContext();
holder.mode.setAdapter(new ModeAdapter(context));
holder.mode.setOnItemSelectedListener(this);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ApplicationsState.AppEntry entry = mEntries.get(position);
if (entry == null) {
return;
}
holder.title.setText(entry.label);
holder.title.setOnClickListener(v -> holder.mode.performClick());
mApplicationsState.ensureIcon(entry);
holder.icon.setImageDrawable(entry.icon);
int packageState = mThermalUtils.getStateForPackage(entry.info.packageName);
holder.mode.setSelection(packageState, false);
holder.mode.setTag(entry);
holder.stateIcon.setImageResource(getStateDrawable(packageState));
}
private void setEntries(List<ApplicationsState.AppEntry> entries,
List<String> sections, List<Integer> positions) {
mEntries = entries;
mSections = sections.toArray(new String[sections.size()]);
mPositions = new int[positions.size()];
for (int i = 0; i < positions.size(); i++) {
mPositions[i] = positions.get(i);
}
notifyDataSetChanged();
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
final ApplicationsState.AppEntry entry = (ApplicationsState.AppEntry) parent.getTag();
int currentState = mThermalUtils.getStateForPackage(entry.info.packageName);
if (currentState != position) {
mThermalUtils.writePackage(entry.info.packageName, position);
notifyDataSetChanged();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
@Override
public int getPositionForSection(int section) {
if (section < 0 || section >= mSections.length) {
return -1;
}
return mPositions[section];
}
@Override
public int getSectionForPosition(int position) {
if (position < 0 || position >= getItemCount()) {
return -1;
}
final int index = Arrays.binarySearch(mPositions, position);
/*
* Consider this example: section positions are 0, 3, 5; the supplied
* position is 4. The section corresponding to position 4 starts at
* position 3, so the expected return value is 1. Binary search will not
* find 4 in the array and thus will return -insertPosition-1, i.e. -3.
* To get from that number to the expected value of 1 we need to negate
* and subtract 2.
*/
return index >= 0 ? index : -index - 2;
}
@Override
public Object[] getSections() {
return mSections;
}
}
private class ActivityFilter implements ApplicationsState.AppFilter {
private final PackageManager mPackageManager;
private final List<String> mLauncherResolveInfoList = new ArrayList<String>();
private ActivityFilter(PackageManager packageManager) {
this.mPackageManager = packageManager;
updateLauncherInfoList();
}
public void updateLauncherInfoList() {
Intent i = new Intent(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfoList = mPackageManager.queryIntentActivities(i, 0);
synchronized (mLauncherResolveInfoList) {
mLauncherResolveInfoList.clear();
for (ResolveInfo ri : resolveInfoList) {
mLauncherResolveInfoList.add(ri.activityInfo.packageName);
}
}
}
@Override
public void init() {
}
@Override
public boolean filterApp(ApplicationsState.AppEntry entry) {
boolean show = !mAllPackagesAdapter.mEntries.contains(entry.info.packageName);
if (show) {
synchronized (mLauncherResolveInfoList) {
show = mLauncherResolveInfoList.contains(entry.info.packageName);
}
}
return show;
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (C) 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.settings.thermal;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.UserHandle;
import androidx.preference.PreferenceManager;
import org.lineageos.settings.utils.FileUtils;
public final class ThermalUtils {
private static final String THERMAL_CONTROL = "thermal_control";
protected static final int STATE_DEFAULT = 0;
protected static final int STATE_BENCHMARK = 1;
protected static final int STATE_BROWSER = 2;
protected static final int STATE_CAMERA = 3;
protected static final int STATE_DIALER = 4;
protected static final int STATE_GAMING = 5;
protected static final int STATE_STREAMING = 6;
private static final String THERMAL_STATE_DEFAULT = "0";
private static final String THERMAL_STATE_BENCHMARK = "10";
private static final String THERMAL_STATE_BROWSER = "11";
private static final String THERMAL_STATE_CAMERA = "12";
private static final String THERMAL_STATE_DIALER = "8";
private static final String THERMAL_STATE_GAMING = "9";
private static final String THERMAL_STATE_STREAMING = "14";
private static final String THERMAL_BENCHMARK = "thermal.benchmark=";
private static final String THERMAL_BROWSER = "thermal.browser=";
private static final String THERMAL_CAMERA = "thermal.camera=";
private static final String THERMAL_DIALER = "thermal.dialer=";
private static final String THERMAL_GAMING = "thermal.gaming=";
private static final String THERMAL_STREAMING = "thermal.streaming=";
private static final String THERMAL_SCONFIG = "/sys/class/thermal/thermal_message/sconfig";
private SharedPreferences mSharedPrefs;
protected ThermalUtils(Context context) {
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
}
public static void startService(Context context) {
context.startServiceAsUser(new Intent(context, ThermalService.class),
UserHandle.CURRENT);
}
private void writeValue(String profiles) {
mSharedPrefs.edit().putString(THERMAL_CONTROL, profiles).apply();
}
private String getValue() {
String value = mSharedPrefs.getString(THERMAL_CONTROL, null);
if (value == null || value.isEmpty()) {
value = THERMAL_BENCHMARK + ":" + THERMAL_BROWSER + ":" + THERMAL_CAMERA + ":" +
THERMAL_DIALER + ":" + THERMAL_GAMING + ":" + THERMAL_STREAMING;
writeValue(value);
}
return value;
}
protected void writePackage(String packageName, int mode) {
String value = getValue();
value = value.replace(packageName + ",", "");
String[] modes = value.split(":");
String finalString;
switch (mode) {
case STATE_BENCHMARK:
modes[0] = modes[0] + packageName + ",";
break;
case STATE_BROWSER:
modes[1] = modes[1] + packageName + ",";
break;
case STATE_CAMERA:
modes[2] = modes[2] + packageName + ",";
break;
case STATE_DIALER:
modes[3] = modes[3] + packageName + ",";
break;
case STATE_GAMING:
modes[4] = modes[4] + packageName + ",";
break;
case STATE_STREAMING:
modes[5] = modes[5] + packageName + ",";
break;
}
finalString = modes[0] + ":" + modes[1] + ":" + modes[2] + ":" + modes[3] + ":" +
modes[4] + ":" + modes[5];
writeValue(finalString);
}
protected int getStateForPackage(String packageName) {
String value = getValue();
String[] modes = value.split(":");
int state = STATE_DEFAULT;
if (modes[0].contains(packageName + ",")) {
state = STATE_BENCHMARK;
} else if (modes[1].contains(packageName + ",")) {
state = STATE_BROWSER;
} else if (modes[2].contains(packageName + ",")) {
state = STATE_CAMERA;
} else if (modes[3].contains(packageName + ",")) {
state = STATE_DIALER;
} else if (modes[4].contains(packageName + ",")) {
state = STATE_GAMING;
} else if (modes[5].contains(packageName + ",")) {
state = STATE_STREAMING;
}
return state;
}
protected void setDefaultThermalProfile() {
FileUtils.writeLine(THERMAL_SCONFIG, THERMAL_STATE_DEFAULT);
}
protected void setThermalProfile(String packageName) {
String value = getValue();
String modes[];
String state = THERMAL_STATE_DEFAULT;
if (value != null) {
modes = value.split(":");
if (modes[0].contains(packageName + ",")) {
state = THERMAL_STATE_BENCHMARK;
} else if (modes[1].contains(packageName + ",")) {
state = THERMAL_STATE_BROWSER;
} else if (modes[2].contains(packageName + ",")) {
state = THERMAL_STATE_CAMERA;
} else if (modes[3].contains(packageName + ",")) {
state = THERMAL_STATE_DIALER;
} else if (modes[4].contains(packageName + ",")) {
state = THERMAL_STATE_GAMING;
} else if (modes[5].contains(packageName + ",")) {
state = THERMAL_STATE_STREAMING;
}
}
FileUtils.writeLine(THERMAL_SCONFIG, state);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2024 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.settings.utils;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
public class ComponentUtils {
/**
* Enables or disables a specified Android component dynamically at runtime.
*
* @param context The context from which the component will be enabled or disabled.
* @param componentClass The class of the component to be enabled or disabled.
* @param enable If true, the component will be enabled; if false, it will be disabled.
*/
public static void toggleComponent(Context context, Class<?> componentClass, boolean enable) {
ComponentName componentName = new ComponentName(context, componentClass);
PackageManager packageManager = context.getPackageManager();
int currentState = packageManager.getComponentEnabledSetting(componentName);
int newState = enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
if (currentState != newState) {
packageManager.setComponentEnabledSetting(
componentName,
newState,
PackageManager.DONT_KILL_APP
);
}
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2016 The CyanogenMod Project
* 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.settings.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);
writer.flush();
} 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;
}
/**
* Writes the given value into the given file.
* @return true on success, false on failure
*/
public static boolean writeValue(String fileName, String value) {
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(fileName));
writer.write(value);
writer.flush();
} 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;
}
/**
* Reads the value from the given file.
* @return the value read from the file, or the default value if an error occurs
*/
public static String getFileValue(String fileName, String defaultValue) {
String value = defaultValue;
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName), 512);
value = 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 value;
}
}

Some files were not shown because too many files have changed in this diff Show More