60 Commits
bq2 ... vic

Author SHA1 Message Date
Kelvin Zhang
d298328f6d sm7125-common: Allow to access bootable/deprecated-ota targets
Sm7125-common is still using non-AB update.
non-AB code will be moved to a separated namespace, add
deprecated-ota to sm6150-common namespace list ahead of time to avoid
breakage.

Test: th
Bug: 324360816
Change-Id: I909064be36e8febe80492cf010eb6eac3d6bc33d
2025-11-30 12:19:19 +05:30
Michael Bestas
d766cad887 sm7125-common: Build recovery QTI health AIDL service
We have a recovery partition.

Change-Id: I10841b26feea3c214349a20b2eedec74924f993a
2025-11-30 12:19:18 +05:30
theshaenix
10164c7973 sm7125-common: Drop the unused android.hardware.power@1.2.vendor 2025-11-30 12:19:18 +05:30
Bruno Martins
c4f6acb349 sm7125-common: Move to QTI health AIDL service
The health AIDL HAL service provides functionalities of charger,
therefore system charger at /system/bin/charger is deprecated.
On top of that, QTI health AIDL HAL service enables suspend by
default, the equivalent of setting ro.charger.enable_suspend
for legacy charger.

Change-Id: I59c23e7974cea1174b0161f31a535fa3afa1e5c9
2025-11-30 12:19:18 +05:30
theshaenix
6012fb4169 sm7125-common: Cleanup 2025-11-30 12:19:18 +05:30
aminfauzi
50dbb8e392 sm7125-common: fingerprint: preload HAL early for first-boot SetupWizard
Add an  trigger to start the fingerprint HAL immediately
after /data is mounted. Keeps  to avoid race
conditions, but ensures the HAL is available early enough for
first-boot SetupWizard enrollment.

This fixes the issue where fingerprint setup fails during initial
device setup but works after skipping and opening Settings.

Signed-off-by: aminfauzi <aremean0107@gmail.com>
2025-11-30 12:19:18 +05:30
theshaenix
a571d2c121 sm7125-common: Enable EPPE 2025-11-30 12:19:17 +05:30
theshaenix
938a92001e sm7125-common: migrate to Adil MultiHAL 2025-11-30 12:19:17 +05:30
Omkar Chandorkar
e02936ba22 sm7125-common: overlay: Steal CarrierConfig from phone1
* From Nothing OS 2.0.4

Change-Id: I8b761a30c86d72d6bd454332ec3042a22c1a7aac
Signed-off-by: Omkar Chandorkar <gotenksIN@aospa.co>
Signed-off-by: Adithya R <gh0strider.2k18.reborn@gmail.com>
Signed-off-by: Mayur <ultramayur123@gmail.com>
2025-11-21 16:57:24 +05:30
theshaenix
262e92a301 sm7125-common: rootdir: kang post_boot from miatolls common tree
* Synced post_boot script from Evolution-X miatolls common tree
  https://github.com/Evolution-X-Devices/device_xiaomi_sm6250-common/blob/bka/rootdir/bin/init.qcom.post_boot-atoll.sh
2025-10-12 19:11:29 +05:30
someone5678
287edef643 sm7125-common: Build vendor.qti.hardware.capabilityconfigstore@1.0
Log:
07-08 14:11:18.637  1300  1300 F linker  : CANNOT LINK EXECUTABLE "/vendor/bin/hw/vendor.qti.hardware.capabilityconfigstore@1.0-service": library "vendor.qti.hardware.capabilityconfigstore@1.0.so" not found: needed by main executable
07-08 14:11:18.724  1366  1366 F linker  : CANNOT LINK EXECUTABLE "/vendor/bin/vppservice": library "vendor.qti.hardware.capabilityconfigstore@1.0.so" not found: needed by /vendor/lib64/libvpplibrary.so in namespace (default)
07-08 14:11:23.638  3100  3100 F linker  : CANNOT LINK EXECUTABLE "/vendor/bin/hw/vendor.qti.hardware.capabilityconfigstore@1.0-service": library "vendor.qti.hardware.capabilityconfigstore@1.0.so" not found: needed by main executable
07-08 14:11:28.644  3177  3177 F linker  : CANNOT LINK EXECUTABLE "/vendor/bin/hw/vendor.qti.hardware.capabilityconfigstore@1.0-service": library "vendor.qti.hardware.capabilityconfigstore@1.0.so" not found: needed by main executable
07-08 14:11:33.651  3194  3194 F linker  : CANNOT LINK EXECUTABLE "/vendor/bin/hw/vendor.qti.hardware.capabilityconfigstore@1.0-service": library "vendor.qti.hardware.capabilityconfigstore@1.0.so" not found: needed by main executable
07-08 14:11:28.395  5816  5816 F linker  : CANNOT LINK EXECUTABLE "/vendor/bin/hw/vendor.qti.hardware.capabilityconfigstore@1.0-service": library "vendor.qti.hardware.capabilityconfigstore@1.0.so" not found: needed by main executable
07-08 14:11:45.348  7112  7112 F linker  : CANNOT LINK EXECUTABLE "/vendor/bin/hw/vendor.qti.hardware.capabilityconfigstore@1.0-service": library "vendor.qti.hardware.capabilityconfigstore@1.0.so" not found: needed by main executable
2025-10-03 17:19:15 +05:30
theshaenix
e0b53bfceb sm7125-common: overlay: Aperture: Ignore redundant aux camera IDs
- Hide camera ID 6 (duplicate 1x lens)
- Hide camera ID 7 (logical/composite stub)
- Prevents duplicate selector and crash when switching lenses
2025-10-02 23:55:31 +05:30
theshaenix
cfd4a67773 sm7125-common: overlay: Allow aux camera package allowlist
- Add config_cameraAuxPackageAllowlist overlay
- Ensures Aperture can access auxiliary lenses
  when vendor aux camera whitelist is present
2025-10-02 23:55:31 +05:30
aminfauzi
002221bc2d sm7125-common: prop: Allow camera apps to access auxiliary camera
This enables camera apps (and other whitelisted apps) to use auxiliary sensors without HAL restrictions, preventing crashes when switching lenses.

Signed-off-by: aminfauzi <aremean0107@gmail.com>
2025-10-02 23:55:30 +05:30
aminfauzi
bd68fd2f5c sm7125-common: overlay: Configure aux camera for Aperture
overlay from rmx2061 repo

Signed-off-by: aminfauzi <aremean0107@gmail.com>
2025-10-02 23:55:30 +05:30
theshaenix
76d32ae34b sm7125-common: sepolicy: drop deprecated rw_fs_type attribute
The rw_fs_type attribute was removed from AOSP sepolicy in newer
Android versions. This causes build failures when declaring new
types. Replace its usage with fs_type to ensure compatibility
with current sepolicy.

Fixes build error:
ERROR 'attribute rw_fs_type is not declared'
2025-10-02 23:55:29 +05:30
aminfauzi
6e67b7d1d2 sm7125-common: sepolicy: Allow system_app to control bypass charging
The Settings app (system_app) requires access to the
 node in order
to enable/disable bypass charging. Without this, SELinux blocks
the access in enforcing mode:

  avc: denied { open } for
  path=/sys/class/power_supply/battery/input_suspend
  scontext=u:r:system_app:s0
  tcontext=u:object_r:vendor_sysfs_battery_supply:s0

Grant system_app rw permissions to vendor_sysfs_battery_supply
to fix bypass charging functionality.

Signed-off-by: aminfauzi <aremean0107@gmail.com>
2025-10-02 23:55:14 +05:30
aminfauzi
1338bf7a9f sm7125-common: sepolicy: Add app_domain declaration for devicesettings_app
Signed-off-by: aminfauzi <aremean0107@gmail.com>
2025-10-02 19:02:45 +05:30
aminfauzi
42ee6eb842 sm7125-common: Fix bypass charging node
Signed-off-by: aminfauzi <aremean0107@gmail.com>
2025-10-02 19:00:44 +05:30
aminfauzi
0e220741ba sm7125-common: parts: Add charging bypass manual toggle [2/2]
Signed-off-by: aminfauzi <aremean0107@gmail.com>
2025-10-02 19:00:44 +05:30
theshaenix
c75adc316a sm7125-common: prop: drop legacy camera EIS prop
- Set EIS enable and type using vendor props only
2025-10-02 18:57:39 +05:30
theshaenix
3241e623c7 sm7125-common: prop: set camera EIS type to 2
From dumpsys media.camera, the vendor tag showed EIS type = 2, which corresponds to:

0 → None
1 → EIS v2
2 → EIS v3
3 → OIS
4 → OIS + EIS

So your blobs natively support EIS v3 (type 2).
If you force is_type=4 (OIS+EIS) but your device only has EIS (no OIS hardware), the HAL may either:
silently fall back to EIS v3 (type 2), or misbehave / crash in certain modes
2025-10-02 18:57:39 +05:30
theshaenix
19bd3280b1 sm7125-common:Initialize EvolutionX 2025-10-01 22:00:01 +05:30
theshaenix
4be9ebc30a sm7125-common: fine tuned the zram 2025-09-23 20:41:20 +05:30
theshaenix
10861470f3 Revert "sm7125-common : integrate Oplus camera vendor blobs"
This reverts commit ecff11a1d3.
2025-09-21 22:06:22 +05:30
theshaenix
74fcd1e347 sm7125-common: rootdir : bin : Aggressive performance & boot optimization for RMX2061
- Fine-tuned GPU devfreq governors and frequencies for dynamic performance
  - Added support for simple_ondemand and msm-adreno-tz
  - AdrenoBoost handling for performance/battery modes
  - Dynamic min/max frequency assignment without killing idle sleep
- Improved ZRAM configuration
  - Auto-sized based on available RAM
  - Multi-threaded compression enabled
  - Aggressive memory management (swappiness 90, vfs_cache_pressure 80)
- VM and memory tweaks
  - Optimized dirty ratios, writeback intervals, overcommit, and compaction
  - Transparent HugePage disabled for faster memory reclaim
  - NUMA balancing disabled for unified memory efficiency
- LMK / RAM management updates
  - Preserves recent apps longer with custom minfree values
  - Extra free memory and watermark tuning
  - CPU cgroup tuning for foreground/background prioritization
- Thermal and network optimizations
  - Conservative thermal trip points to reduce throttling
  - TCP/IP stack and buffer tuning for low latency
- Bootspeed improvements
  - Suppressed printk, RCU and debug overhead
  - Disabled NMI watchdog and tracing for faster initialization
- Overall: Enhanced user experience for both gaming and everyday usage
  - Aggressive memory & GPU management without sacrificing idle or background tasks
2025-09-21 22:06:14 +05:30
theshaenix
f009924a41 sm7125-common: ship excluded-input-devices.xml
- Add PRODUCT_COPY_FILES entry to install excluded-input-devices.xml
  into /vendor/etc
- Ensures vibrator HAL ignores blacklisted input devices
- Prevents duplicate or unintended haptic feedback
- Improves consistency of vibration across UI and apps
2025-09-17 23:17:01 +05:30
theshaenix
cb944af083 sm7125-common: configs: raise min blocks-per-second limit
- Increase blocks-per-second range from 24–979200 to 36–979200
- Prevents codec operation at extremely low block rates
- Reduces risk of underruns and playback instability
- Improves scheduling efficiency and smoother video performance

Trade-off: drops support for extreme low-FPS edge cases, but ensures
better stability across typical playback/recording scenarios.
2025-09-17 23:17:00 +05:30
theshaenix
9d5d4d111b sm7125-common: Switch to dot product CPU variant
* sm7125:/ $ cat /proc/cpuinfo | grep -m 1 Features
   Features: fp asimd evtstrm aes pmull sha1 sha2 crc32
   atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp

 * From this, it's evident that our CPU (Snapdragon 720G)
   supports all the features of ARMv8.2-DotProd [1].
   Moreover AOSP sets this arch variant in their generic
   cortex-a55 ART target as well [2]. This should unlock
   some optimizations in different code paths.
[1]:
https://en.wikichip.org/wiki/arm/armv8#ARMv8_Extensions_and_Processor_Features
[2]:
https://android.googlesource.com/device/generic/art/+/refs/heads/master/armv8_cortex_a55/BoardConfig.mk#23
2025-09-17 23:17:00 +05:30
theshaenix
ecff11a1d3 sm7125-common : integrate Oplus camera vendor blobs
- Cloned proprietary_vendor_oplus_camera repo by pjgowtham
  (https://gitlab.com/pjgowtham/proprietary_vendor_oplus_camera.git)
- Added vendor/oplus/camera directory
- Updated device tree to include
2025-09-17 23:16:59 +05:30
theshaenix
d1f4474d48 sm7125-common: rootdir : bin: tune CPU, scheduler and memory
- Enable ZRAM with LZ4 compression and tuned swappiness
- Adjust CPU hispeed freqs and minimums for better responsiveness
- Configure schedutil governor for both clusters
- Lower migration thresholds for smoother task distribution
- Tune core_ctl for balanced performance vs. idle drain
- Re-enable sleep states for improved standby battery
- Set up bw_hwmon and mem_latency governors on interconnects
- Cleaned out unnecessary/duplicate entries from script

Overall effect: smoother UI, faster app response, and better
multitasking with minimal idle power impact.
2025-09-17 23:16:48 +05:30
theshaenix
7ae39dc780 sm7125-common:Switch to android.hardware.sensors@2.1 multihal
The legacy 2.0 multihal does not support ScopedWakelock handling,
causing non-wakeup sensors (e.g. BMI160 gyroscope) to stall unless
the screen is actively touched.

Migrate to android.hardware.sensors@2.1-service.multihal_realme_sm7125
to properly integrate ScopedWakelock and per-event wakelock tracking.

This resolves issues where the gyroscope only reported data
while the device was awake, ensuring continuous event delivery
even when idle.

Tested:
- Gyroscope events stream without requiring screen touches
- dumpsys sensorservice shows correct flags and batching
- No regressions observed in other HAL sensors
2025-09-05 21:10:17 +05:30
theshaenix
a1c336578f sm7125-common: sepolicy: added new rules for bypass charging 2025-09-03 22:02:40 +05:30
theshaenix
1d703b5ffa sm7125-common: reduced the status bar padding top 2025-09-03 22:02:39 +05:30
anhdat1024
97aac6da03 sm7125-common: overlay: Adjust status bar height 2025-09-03 22:02:39 +05:30
theshaenix
8bae3deb28 sm7125-common: props: Use EIS for camera
* Fixes gcam video stabilization
2025-09-03 22:02:38 +05:30
theshaenix
e1bca51105 sm7125-common : rootdir: Optimize CPU governor, sched, and core control for Snapdragon 720G
- Replaced static performance governor with dynamic schedutil for all cores
    - Tuned up_rate_limit_us and down_rate_limit_us for smooth everyday performance
    - Set scaling_min and scaling_max frequencies according to Kryo 465 Gold/Silver cores
    - Configured core_ctl for little cluster (min_cpus, busy thresholds, offline delay)
    - Disabled core_ctl on big cluster to let scheduler manage ramp-up efficiently
    - Adjusted b.L scheduler parameters (sched_downmigrate, sched_upmigrate, group migrate)
    - Set sched_little_cluster_coloc_fmin_khz for smoother colocation
    - Applied sched_load_boost per CPU for better responsiveness
    - Configured input boost frequency and duration for UI snappiness
    - Updated memory and bus DCVS parameters for balanced performance/power
    - Maintained conservative power optimizations (cpusets, sleep modes, kswapd, I/O scheduler)

    This commit ensures a universal setup for normal daily use and gaming while
    avoiding overheating and maintaining high FPS
2025-09-03 22:02:21 +05:30
aminfauzi
951ae51350 sm7125-common:audio: Sync with Spatial Audio
Signed-off-by: aminfauzi <aremean0107@gmail.com>
2025-09-03 12:47:03 +05:30
theshaenix
747995f07d sm7125-common: overlay: Enable Wifi display 2025-09-03 12:47:03 +05:30
theshaenix
17662c88cc sm7125-common: sm7125-common: overlay: Speed up animations
On low powered, legacy devices, the animations are a bit sluggish. Set the animation scales to 50% to make the UI feel snappier.
2025-09-03 12:46:55 +05:30
Skyblueborb
3e2560075a sm7125-common: Label cust partition
Fixes flashing `cust` through fastbootd.

Change-Id: I1a950037dafa99c5e7b4305dda4aa8d35fce143b
Signed-off-by: Skyblueborb <tomaszborbely0710@gmail.com>
2025-09-02 22:42:18 +05:30
theshaenix
94f75e5f22 sm7125-common: Disable scudo to save ram 2025-09-02 22:38:33 +05:30
theshaenix
e78b5be3f2 sm7125-common: Switch to AIDL LiveDisplay HAL 2025-09-02 22:38:27 +05:30
LuK1337
fbd5f1a416 sm7125-common: Set SSR restart_level in init.qcom.rc
Change-Id: I31e174f8c3647c17152a2b225b1bb524e22e07aa
2025-09-02 22:18:22 +05:30
theshaenix
601baf8ab8 Revert "sm7125--common: Commonize schedtune and cpuset configuration"
This reverts commit 13f221fb28.
2025-09-02 21:55:51 +05:30
theshaenix
7e9d524ad6 sm7125-common: Disable EPPE targets 2025-09-01 21:28:46 +05:30
theshaenix
c8176ec118 sm7125-common: Added new allow rules to sepolicy 2025-09-01 21:28:46 +05:30
theshaenix
f71da59c08 sm7125-common: overlay: Enable BoostFramework support 2025-09-01 21:28:45 +05:30
theshaenix
4501bce205 sm7125-common: switch to AIDL fingerprint HAL 2025-09-01 21:28:35 +05:30
Arian
1e1434cd33 sm7125-common: rootdir: Enable PowerHAL hint processing
Change-Id: I4de6ec81eaf002c094df53ff26ed24db02972dfe
2025-08-31 09:55:14 +05:30
justinntime
eb62287e5e sm7125-common: parts: Change qs tile handling, merge charging and info folders 2025-08-30 20:05:16 +05:30
justinntime
12b640f71a sm7125-common: parts: Introduce Charging Control v2 2025-08-30 20:04:43 +05:30
justinntime
5c65a3d6bc sm7125-common: parts: Add missing label and reorder 2025-08-30 20:04:17 +05:30
kamikaonashi
136de7c630 sm7125-common: parts: Add charging bypass manual toggle [1/2] 2025-08-30 20:00:10 +05:30
justinntime
f83c0560d9 sm7125-common: parts: Introduce Charging info tile functionality [1/2] 2025-08-30 19:59:50 +05:30
justinntime
7b8f8e55e5 sm7125-common: parts: Remove refresh rate functionality 2025-08-30 19:58:19 +05:30
kenway214
5dae09ef5e sm7125-common: parts: Update GameBar to 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
2025-08-30 19:56:30 +05:30
just_inntime
419b851b90 sm7125-common: parts: Prevent crash, log errors 2025-08-30 19:55:55 +05:30
just_inntime
50f647e811 sm7125-common: parts: Change thermal zone from 0 to 19 2025-08-30 19:55:27 +05:30
just_inntime
993493dc3c sm7125-common: Introduce parts [1/2]
Based on XiaomiParts for peridot
Special thanks to kenway214
2025-08-30 19:54:11 +05:30
89 changed files with 8496 additions and 7174 deletions

View File

@@ -2,6 +2,7 @@ soong_namespace {
imports: [
"hardware/google/interfaces",
"hardware/google/pixel",
"bootable/deprecated-ota",
],
}

View File

@@ -32,17 +32,17 @@ BUILD_BROKEN_INCORRECT_PARTITION_IMAGES := true
# Architecture
TARGET_ARCH := arm64
TARGET_ARCH_VARIANT := armv8-a
TARGET_ARCH_VARIANT := armv8-2a-dotprod
TARGET_CPU_ABI := arm64-v8a
TARGET_CPU_ABI2 :=
TARGET_CPU_VARIANT := generic
TARGET_CPU_VARIANT := cortex-a76
TARGET_CPU_VARIANT_RUNTIME := cortex-a76
TARGET_2ND_ARCH := arm
TARGET_2ND_ARCH_VARIANT := armv8-a
TARGET_2ND_ARCH_VARIANT := armv8-2a
TARGET_2ND_CPU_ABI := armeabi-v7a
TARGET_2ND_CPU_ABI2 := armeabi
TARGET_2ND_CPU_VARIANT := generic
TARGET_2ND_CPU_VARIANT := cortex-a76
TARGET_2ND_CPU_VARIANT_RUNTIME := cortex-a76
# Audio

View File

@@ -52,6 +52,9 @@
<!--DOLBY VQE-->
<library name="vqe" path="libswvqe.so"/>
<!--DOLBY END-->
<!--Spatial Audio-->
<library name="spatializer" path="libspatialaudio.so"/>
<!--Spatial Audio-->
</libraries>
<effects>
<effectProxy name="bassboost" library="proxy" uuid="14804144-a5ee-4d24-aa88-0002a5d5c51b">
@@ -116,6 +119,9 @@
<!--DOLBY VQE-->
<effect name="vqe" library="vqe" uuid="64a0f614-7fa4-48b8-b081-d59dc954616f"/>
<!--DOLBY END-->
<!--Spatial Audio-->
<effect name="spatializer" library="spatializer" uuid="cc4677de-ff72-11eb-9a03-0242ac130003"/>
<!--Spatial Audio-->
</effects>
<postprocess>
<stream type="music">

View File

@@ -81,6 +81,12 @@
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
<mixPort name="hifi_playback" role="source" />
<!--Spatial Audio-->
<mixPort name="immersive_out" role="source" flags="AUDIO_OUTPUT_FLAG_SPATIALIZER">
<profile name="" format="AUDIO_FORMAT_PCM_FLOAT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
<!--Spatial Audio-->
</mixPort>
<mixPort name="compress_passthrough" role="source"
flags="AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD|AUDIO_OUTPUT_FLAG_NON_BLOCKING">
<profile name="" format="dynamic"
@@ -322,11 +328,11 @@
<route type="mix" sink="Earpiece"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,mmap_no_irq_out"/>
<route type="mix" sink="Speaker"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,mmap_no_irq_out"/>
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,mmap_no_irq_out,immersive_out"/>
<route type="mix" sink="Wired Headset"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,dsd_compress_passthrough,voip_rx,mmap_no_irq_out"/>
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,dsd_compress_passthrough,voip_rx,mmap_no_irq_out,immersive_out"/>
<route type="mix" sink="Wired Headphones"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,dsd_compress_passthrough,voip_rx,mmap_no_irq_out"/>
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,dsd_compress_passthrough,voip_rx,mmap_no_irq_out,immersive_out"/>
<route type="mix" sink="Line"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,dsd_compress_passthrough,voip_rx,mmap_no_irq_out"/>
<route type="mix" sink="HDMI"
@@ -336,15 +342,15 @@
<route type="mix" sink="FM"
sources="primary output"/>
<route type="mix" sink="BT SCO"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx"/>
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,immersive_out"/>
<route type="mix" sink="BT SCO Headset"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx"/>
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,immersive_out"/>
<route type="mix" sink="BT SCO Car Kit"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx"/>
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,immersive_out"/>
<route type="mix" sink="USB Device Out"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,mmap_no_irq_out,hifi_playback"/>
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,mmap_no_irq_out,hifi_playback,immersive_out"/>
<route type="mix" sink="USB Headset Out"
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,mmap_no_irq_out,hifi_playback"/>
sources="primary output,raw,deep_buffer,direct_pcm,compressed_offload,voip_rx,mmap_no_irq_out,hifi_playback,immersive_out"/>
#else VENDOR_EDIT -->
<route type="mix" sink="Earpiece"
sources="primary output,deep_buffer,direct_pcm,compressed_offload,voip_rx,mmap_no_irq_out"/>

View File

@@ -66,12 +66,6 @@ PRODUCT_COPY_FILES += \
$(call find-copy-subdir-files,*,$(LOCAL_PATH)/audio/odm/,$(TARGET_COPY_OUT_ODM)/etc) \
$(call find-copy-subdir-files,*,$(LOCAL_PATH)/audio/vendor/,$(TARGET_COPY_OUT_VENDOR)/etc)
# AVB
#PRODUCT_PACKAGES += \
# q-gsi.avbpubkey \
# r-gsi.avbpubkey \
# s-gsi.avbpubkey
# Bluetooth
PRODUCT_PACKAGES += \
audio.bluetooth.default
@@ -90,10 +84,6 @@ PRODUCT_COPY_FILES += \
TARGET_SCREEN_HEIGHT := 2400
TARGET_SCREEN_WIDTH := 1080
# Call recording
#PRODUCT_PACKAGES += \
# com.google.android.apps.dialer.call_recording_audio.features.xml
# Camera
PRODUCT_PACKAGES += \
android.hardware.camera.provider@2.4-impl \
@@ -109,9 +99,18 @@ PRODUCT_COPY_FILES += \
#CertifiedProps
$(call inherit-product-if-exists, vendor/certprops/Android.mk)
# Display
# Core Packages
PRODUCT_PACKAGES += \
Parts
# Config Store
PRODUCT_PACKAGES += \
disable_configstore \
vendor.qti.hardware.capabilityconfigstore@1.0 \
vendor.qti.hardware.capabilityconfigstore@1.0.vendor
# Display
PRODUCT_PACKAGES += \
gralloc.qcom \
hwcomposer.qcom \
libdisplayconfig.qti \
@@ -189,8 +188,8 @@ PRODUCT_COPY_FILES += \
# Health
PRODUCT_PACKAGES += \
android.hardware.health@2.1-impl-qti \
android.hardware.health@2.1-service
android.hardware.health-service.qti \
android.hardware.health-service.qti_recovery
# HIDL
PRODUCT_PACKAGES += \
@@ -271,9 +270,11 @@ PRODUCT_PACKAGES += \
#Lawnchair
$(call inherit-product-if-exists, vendor/lawnchair/lawnchair.mk)
# LiveDisplay
# PRODUCT_PACKAGES += \
# vendor.lineage.livedisplay@2.0-service-sdm
#LiveDisplay
PRODUCT_PACKAGES += \
vendor.lineage.livedisplay-service.sdm
$(call soong_config_set,livedisplay_sdm,enable_dm,false)
# Media
PRODUCT_PACKAGES += \
@@ -329,7 +330,8 @@ PRODUCT_PACKAGES += \
# Overlays
DEVICE_PACKAGE_OVERLAYS += \
$(LOCAL_PATH)/overlay \
$(LOCAL_PATH)/overlay-lineage
$(LOCAL_PATH)/overlay-lineage \
$(LOCAL_PATH)/overlay-evolution
# Perf
PRODUCT_PACKAGES += \
@@ -345,7 +347,6 @@ PRODUCT_PROPERTY_OVERRIDES += \
# Power
PRODUCT_PACKAGES += \
android.hardware.power-service.lineage-libperfmgr \
android.hardware.power@1.2.vendor \
libqti-perfd-client
PRODUCT_COPY_FILES += \
@@ -383,8 +384,8 @@ PRODUCT_PACKAGES += \
# Sensors
PRODUCT_PACKAGES += \
android.hardware.sensors@2.0-service.multihal_realme_sm7125
android.hardware.sensors-service.multihal
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.sensor.accelerometer.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.accelerometer.xml \
frameworks/native/data/etc/android.hardware.sensor.compass.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.compass.xml \
@@ -402,12 +403,16 @@ PRODUCT_PACKAGES += \
vendor.qti.hardware.servicetracker@1.1.vendor \
vendor.qti.hardware.servicetracker@1.2.vendor
# Disable Scudo to save RAM.
PRODUCT_DISABLE_SCUDO := true
# Soong namespaces
PRODUCT_SOONG_NAMESPACES += \
$(LOCAL_PATH) \
kernel/realme/sm7125 \
hardware/google/interfaces \
hardware/google/pixel \
bootable/deprecated-ota \
hardware/lineage/interfaces/power-libperfmgr \
hardware/oplus \
hardware/qcom-caf/common/libqti-perfd-client
@@ -454,10 +459,6 @@ PRODUCT_PACKAGES += \
PRODUCT_PACKAGES += \
android.hardware.thermal-service.qti
# TextClassifier
#PRODUCT_PACKAGES += \
# textclassifier.bundle1
# Touchscreen
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.touchscreen.multitouch.jazzhand.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.touchscreen.multitouch.jazzhand.xml
@@ -477,6 +478,9 @@ PRODUCT_COPY_FILES += \
PRODUCT_PACKAGES += \
vendor.qti.hardware.vibrator.service
PRODUCT_COPY_FILES += \
vendor/qcom/opensource/vibrator/excluded-input-devices.xml:$(TARGET_COPY_OUT_VENDOR)/etc/excluded-input-devices.xml
PRODUCT_COPY_FILES += \
prebuilts/vndk/v30/arm64/arch-arm64-armv8-a/shared/vndk-core/libgui.so:$(TARGET_COPY_OUT_SYSTEM)/lib64/libxxx.so \
prebuilts/vndk/v30/arm64/arch-arm-armv8-a/shared/vndk-core/libgui.so:$(TARGET_COPY_OUT_SYSTEM)/lib/libxxx.so \
@@ -525,9 +529,6 @@ PRODUCT_PACKAGES += \
firmware_wlan_mac.bin_symlink \
firmware_WCNSS_qcom_cfg.ini_symlink
#PRODUCT_BOOT_JARS += \
WfdCommon
# Build Specific VNDK 30 Libs for Android 12+
PRODUCT_PACKAGES += \
android.frameworks.automotive.display@1.0.vendor \

View File

@@ -64,7 +64,7 @@
<Limit name="size" min="96x96" max="4096x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="blocks-per-second" range="24-979200" />
<Limit name="blocks-per-second" range="36-979200" />
<Limit name="bitrate" range="1-100000000" />
<Limit name="frame-rate" range="1-240" />
<Limit name="concurrent-instances" max="16" />

View File

@@ -1,7 +0,0 @@
[
{
"repository": "crdroidandroid/android_hardware_dolby",
"target_path": "hardware/dolby",
"branch": "15.0"
}
]

View File

@@ -2,7 +2,10 @@ service vendor.fps_hal.realme_sm7125 /vendor/bin/hw/android.hardware.biometrics.
# "class hal" causes a race condition on some devices due to files created
# in /data. As a workaround, postpone startup until later in boot once
# /data is mounted.
class late_start
class hal
user system
group system input uhid
writepid /dev/cpuset/system-background/tasks
on post-fs-data
start vendor.fps_hal.realme_sm7125

View File

@@ -659,15 +659,6 @@
<instance>oplus_slot2</instance>
</interface>
</hal>
<!-- <hal format="hidl">
<name>vendor.lineage.livedisplay</name>
<transport>hwbinder</transport>
<version>2.0</version>
<interface>
<name>IPictureAdjustment</name>
<instance>default</instance>
</interface>
</hal> -->
<hal format="hidl">
<name>vendor.qti.hardware.data.iwlan</name>
<transport>hwbinder</transport>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019-2025 The Evolution X Project
SPDX-License-Identifier: Apache-2.0
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Android version screen, build maintainer -->
<string name="build_maintainer_summary" translatable="false">SHAHID_SHAMIM</string>
<string name="build_maintainer_donate_url" translatable="false">https://paypal.me/shaenix</string>
</resources>

View File

@@ -550,9 +550,6 @@
<!-- Whether to show min/max refresh rate in display settings -->
<bool name="config_show_refresh_rate_controls">true</bool>
<!-- Indicates whether device has a power button fingerprint sensor. -->
<bool name="config_is_powerbutton_fps" translatable="false" >true</bool>
<!-- Control the behavior when the user long presses the power button.
0 - Nothing
1 - Global actions menu
@@ -592,4 +589,21 @@
<!-- Whether we use large screen shade header which takes only one row compared to QS header -->
<bool name="config_use_large_screen_shade_header">true</bool>
</resources>
<!-- Boost Framework -->
<bool name="config_supportsBoostFramework">true</bool>
<!-- Whether WiFi display is supported by this device.
There are many prerequisites for this feature to work correctly.
Here are a few of them:
* The WiFi radio must support WiFi P2P.
* The WiFi radio must support concurrent connections to the WiFi display and
to an access point.
* The Audio Server audio_policy_configuration.xml file must specify a rule for
the "r_submix" remote submix module. This module is used to record and stream system
audio output to the WiFi display encoder in the media server.
* The remote submix module "audio.r_submix.default" must be installed on the device.
* The device must be provisioned with HDCP keys (for protected content).
-->
<bool name="config_enableWifiDisplay">true</bool>
</resources>

View File

@@ -15,10 +15,15 @@
* limitations under the License.
*/
-->
<resources>
<!-- Height of the status bar. Do not read this dimen directly. Use {@link SystemBarUtils#getStatusBarHeight} instead.-->
<dimen name="status_bar_height">100px</dimen>
<!-- Height of the status bar in portrait -->
<dimen name="status_bar_height_portrait">110px</dimen>
<dimen name="status_bar_height_portrait">100px</dimen>
<!-- Height of the status bar in landscape -->
<dimen name="status_bar_height_landscape">82px</dimen>

View File

@@ -39,5 +39,7 @@
<!-- Enable vibrate for calls by default -->
<bool name="def_vibrate_when_ringing">true</bool>
<!-- Speed up the animations -->
<fraction name="def_window_transition_scale">50%</fraction>
</resources>

View File

@@ -23,7 +23,7 @@
<dimen name="status_bar_padding_end">16px</dimen>
<!-- the padding on the top of the statusbar -->
<dimen name="status_bar_padding_top">8dp</dimen>
<dimen name="status_bar_padding_top">4dp</dimen>
<!-- Starting padding for a left-aligned status bar clock -->
<dimen name="status_bar_left_clock_starting_padding">6px</dimen>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Enable auxiliary cameras selector. -->
<bool name="config_enableAuxCameras">true</bool>
<!-- List of ID of auxiliary cameras that must be ignored by the app. -->
<string-array name="config_ignoredAuxCameraIds">
<item>6</item>
<item>7</item>
</string-array>
<!-- List of ID of camera devices that doesn't advertise
REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE yet are
able to produce a JPEG or a YUV stream. The camera IDs specified
here won't be excluded by CameraX. Please be sure to test all
modes exposed by the app with those cameras. -->
<!-- <string-array name="config_backwardCompatibleCameraIds">
<item>30</item>
</string-array> -->
<!-- New flag to allow aperture in aux package allowlist -->
<bool name="config_cameraAuxPackageAllowlist">true</bool>
</resources>

File diff suppressed because it is too large Load Diff

View File

@@ -24,14 +24,6 @@
devices will be able to vary their amplitude but do not possess enough dynamic range to
have distinct intensity levels -->
<bool name="config_vibration_supports_multiple_intensities">true</bool>
<!-- Defines the location of the fingerprint sensor on the device
0 = back
1 = front
2 = left side
3 = right side
-->
<integer name="config_fingerprintSensorLocation">3</integer>
<!-- Whether this device is supporting the microphone toggle -->
<bool name="config_supportsMicToggle">true</bool>

43
parts/Android.bp Normal file
View File

@@ -0,0 +1,43 @@
//
// Copyright (C) 2017-2020 The LineageOS Project
//
// SPDX-License-Identifier: Apache-2.0
//
android_app {
name: "Parts",
defaults: [
"SettingsLibDefaults",
],
srcs: [
"src/**/*.kt",
"src/**/*.java"
],
certificate: "platform",
resource_dirs: ["res"],
platform_apis: true,
system_ext_specific: true,
privileged: true,
static_libs: [
"androidx.core_core",
"org.lineageos.settings.resources"
],
optimize: {
proguard_flags_files: ["proguard.flags"],
},
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,
}

210
parts/AndroidManifest.xml Normal file
View File

@@ -0,0 +1,210 @@
<?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.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.BATTERY_STATS" />
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-sdk
android:minSdkVersion="24"
android:targetSdkVersion="30"/>
<application
android:label="@string/app_title"
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>
<!-- 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>
<!-- Charging Bypass Activity -->
<activity
android:name=".charge.ChargeActivity"
android:label="@string/charge_bypass_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.battery" />
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/charge_bypass_summary" />
</activity>
<!-- Charging Bypass Monitoring Service -->
<service
android:name=".charge.ChargeBatteryMonitorService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="battery_optimization" />
</service>
<!-- Charging Bypass Boot Receiver -->
<receiver
android:name=".charge.ChargeReceiver"
android:enabled="true"
android:exported="false">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- Charging Bypass Quick Settings Tile -->
<service
android:name=".charge.ChargeQSTile"
android:enabled="true"
android:exported="true"
android:icon="@drawable/ic_charge_bypass"
android:label="@string/charge_bypass_qs_tile_label"
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.ACTIVE_TILE"
android:value="false" />
</service>
<!-- Charging information tile service -->
<service
android:name=".charginginfo.ChargingInfoTileService"
android:icon="@drawable/ic_charging_speed"
android:label="@string/charging_tile_label"
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="false" />
</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

@@ -1,24 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 The LineageOS Project
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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Defines the location of the fingerprint sensor on the device
0 = back
1 = front
2 = left side
3 = right side
-->
<integer name="config_fingerprintSensorLocation">3</integer>
</resources>
<permissions>
<privapp-permissions package="org.lineageos.settings">
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
</permissions>

0
parts/proguard.flags Normal file
View File

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="@android:color/white"
android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zM15,20H9V6h6V20z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M9,8h6v2H9z"/>
</vector>

View File

@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zM15,20H9V6h6V20z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M13,13a1,1 0 1,1 -2,0a1,1 0 1,1 2,0"/>
<path
android:fillColor="@android:color/white"
android:pathData="M12,10.5c-0.83,0 -1.5,0.67 -1.5,1.5h1c0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,0.5 -0.75,0.75 -0.75,1.5h1c0,-0.38 0.75,-0.75 0.75,-1.5C13.5,11.17 12.83,10.5 12,10.5z"/>
</vector>

View File

@@ -0,0 +1,26 @@
<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/colorOnSurface">
<!-- Battery outline -->
<path
android:fillColor="@android:color/white"
android:pathData="M15.67,4H14V2c0,-0.55 -0.45,-1 -1,-1h-2c-0.55,0 -1,0.45 -1,1v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4z"/>
<!-- Bypass indicator (diagonal line) -->
<path
android:fillColor="@android:color/black"
android:pathData="M6,6l12,12"
android:strokeColor="@android:color/white"
android:strokeWidth="1.5"
android:strokeLineCap="round"/>
<!-- Lightning bolt (partially visible) -->
<path
android:fillColor="@android:color/black"
android:pathData="M11,9l1.5,0l-1.5,3l1,0l-1.5,3l-1.5,0l1.5,-3l-1,0z"/>
</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="@android:color/white"
android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4z"/>
<path
android:fillColor="@android:color/black"
android:pathData="M11,20v-5.5H9L13,7v5.5h2L11,20z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</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,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,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>

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

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2025 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<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,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MinMaxSeekBarPreference">
<attr name="minValue" format="integer" />
<attr name="maxValue" format="integer" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,90 @@
<?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">
<!-- Core Settings -->
<string name="app_title">Core 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>
<!-- 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>
<!-- Charging information tile -->
<string name="charging_tile_label">Charging Speed</string>
<string name="charging_tile_information">Shows real-time charging/discharging power</string>
<!-- Tile states -->
<string name="charging_tile_charging">Charging</string>
<string name="charging_tile_discharging">Discharging</string>
<string name="charging_tile_not_charging">Not Charging</string>
<string name="charging_tile_error">Error</string>
<!-- Content descriptions for accessibility -->
<string name="charging_tile_desc_charging">Charging at %1$s watts, %2$s amps, %3$s volts</string>
<string name="charging_tile_desc_discharging">Discharging at %1$s watts</string>
<string name="charging_tile_desc_not_charging">Battery not charging or discharging</string>
<string name="charging_tile_desc_error">Unable to read charging information</string>
<!-- Charge bypass strings -->
<string name="charge_bypass_title">Charging Bypass</string>
<string name="charge_bypass_summary">Configure charging bypass settings</string>
<string name="charge_bypass_warning">Enabling charging bypass will prevent the battery from charging while plugged in. This may affect battery longevity if used incorrectly.</string>
<string name="charge_bypass_unavailable">Charging bypass is not supported on this device</string>
<!-- Master toggle strings -->
<string name="charge_bypass_master_title">Enable Charging Bypass</string>
<string name="charge_bypass_master_summary">Allow charging bypass based on selected configuration</string>
<!-- Configuration category -->
<string name="charge_bypass_configuration_title">Bypass Configuration</string>
<!-- Manual mode strings -->
<string name="charge_bypass_manual_title">Manual Control</string>
<string name="charge_bypass_manual_summary">Control charging bypass through Quick Settings tile</string>
<!-- Smart mode strings -->
<string name="charge_bypass_smart_title">Smart Control</string>
<string name="charge_bypass_smart_summary">Automatically manage charging bypass based on battery level</string>
<!-- Battery target strings -->
<string name="charge_bypass_battery_target_title">Battery Target</string>
<string name="charge_bypass_battery_target_summary">Bypass charging when battery reaches this level (%d%%)</string>
<!-- QS Tile strings -->
<string name="charge_bypass_qs_tile_label">Charging Bypass</string>
<string name="charge_bypass_qs_active">Bypass Active</string>
<string name="charge_bypass_qs_inactive">Bypass Inactive</string>
<string name="charge_bypass_qs_disabled">Bypass Disabled</string>
<!-- Notification strings -->
<string name="charge_bypass_notification_title">Charging Bypass Active</string>
<string name="charge_bypass_notification_text">Battery charging is currently bypassed</string>
<string name="charge_bypass_smart_enabled">Smart charging bypass enabled at %d%%</string>
<string name="charge_bypass_smart_disabled">Smart charging bypass disabled</string>
</resources>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Main bypass toggle -->
<SwitchPreference
android:key="bypass_charge_master"
android:title="@string/charge_bypass_master_title"
android:summary="@string/charge_bypass_master_summary"
android:defaultValue="false" />
<!-- Configuration category -->
<PreferenceCategory
android:key="bypass_configuration_category"
android:title="@string/charge_bypass_configuration_title">
<!-- Manual bypass option -->
<CheckBoxPreference
android:key="bypass_mode_manual"
android:title="@string/charge_bypass_manual_title"
android:summary="@string/charge_bypass_manual_summary"
android:defaultValue="true" />
<!-- Smart bypass option -->
<CheckBoxPreference
android:key="bypass_mode_smart"
android:title="@string/charge_bypass_smart_title"
android:summary="@string/charge_bypass_smart_summary"
android:defaultValue="false" />
</PreferenceCategory>
<!-- Battery target slider (only visible in smart mode) -->
<org.lineageos.settings.charge.MinMaxSeekBarPreference
android:key="battery_target_slider"
android:title="@string/charge_bypass_battery_target_title"
android:summary="@string/charge_bypass_battery_target_summary"
android:min="50"
android:max="100"
android:defaultValue="80"
android:dependency="bypass_mode_smart" />
</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,71 @@
/*
* 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.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
public class BootCompletedReceiver extends BroadcastReceiver {
private static final String TAG = "Parts";
private static final boolean DEBUG = true;
@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:
handleLockedBootCompleted(context);
break;
case Intent.ACTION_BOOT_COMPLETED:
handleBootCompleted(context);
break;
}
}
private void handleLockedBootCompleted(Context context) {
if (DEBUG) Log.i(TAG, "Handling locked boot completed.");
try {
// Start necessary services
startServices(context);
} catch (Exception e) {
Log.e(TAG, "Error during locked boot completed processing", e);
}
}
private void handleBootCompleted(Context context) {
if (DEBUG) Log.i(TAG, "Handling boot completed.");
// Add additional boot-completed actions if needed
}
private void startServices(Context context) {
if (DEBUG) Log.i(TAG, "Starting services...");
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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;
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;
import org.lineageos.settings.charge.ChargeActivity;
import org.lineageos.settings.charge.ChargeQSTile;
import org.lineageos.settings.charginginfo.ChargingInfoTileService;
public final class TileHandlerActivity extends Activity {
private static final String TAG = "TileHandlerActivity";
// Map QS Tile services to their corresponding activity
private static final Map<String, Class<?>> TILE_ACTIVITY_MAP = new HashMap<>();
static {
TILE_ACTIVITY_MAP.put(GameBarTileService.class.getName(), GameBarSettingsActivity.class);
TILE_ACTIVITY_MAP.put(ChargeQSTile.class.getName(), ChargeActivity.class);
TILE_ACTIVITY_MAP.put(ChargingInfoTileService.class.getName(), ChargeActivity.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,45 @@
/*
* 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.charge;
import android.os.Bundle;
import android.view.MenuItem;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
public class ChargeActivity extends CollapsingToolbarBaseActivity {
private static final String TAG_BYPASS_CHARGE = "bypass_charge";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportFragmentManager().beginTransaction().replace(
com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new ChargeSettingsFragment(), TAG_BYPASS_CHARGE).commit();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,209 @@
/*
* 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.charge;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.IBinder;
import android.util.Log;
import org.lineageos.settings.R;
public class ChargeBatteryMonitorService extends Service {
private static final String TAG = "ChargeBatteryMonitorService";
private static final String NOTIFICATION_CHANNEL_ID = "charge_bypass_channel";
private static final int NOTIFICATION_ID = 1001;
private ChargeUtils mChargeUtils;
private BatteryReceiver mBatteryReceiver;
private NotificationManager mNotificationManager;
private boolean mIsMonitoring = false;
@Override
public void onCreate() {
super.onCreate();
mChargeUtils = new ChargeUtils(this);
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
createNotificationChannel();
Log.d(TAG, "Service created");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!mChargeUtils.isMasterToggleEnabled()) {
Log.d(TAG, "Master toggle disabled, stopping service");
stopSelf();
return START_NOT_STICKY;
}
if (!mIsMonitoring) {
startMonitoring();
}
return START_STICKY; // Restart service if killed
}
@Override
public void onDestroy() {
super.onDestroy();
stopMonitoring();
Log.d(TAG, "Service destroyed");
}
@Override
public IBinder onBind(Intent intent) {
return null; // We don't provide binding
}
private void startMonitoring() {
if (mIsMonitoring) return;
mBatteryReceiver = new BatteryReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(Intent.ACTION_POWER_CONNECTED);
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
registerReceiver(mBatteryReceiver, filter);
mIsMonitoring = true;
// Initial check
mChargeUtils.updateBypassState();
updateNotification();
Log.d(TAG, "Started battery monitoring");
}
private void stopMonitoring() {
if (!mIsMonitoring) return;
if (mBatteryReceiver != null) {
unregisterReceiver(mBatteryReceiver);
mBatteryReceiver = null;
}
mIsMonitoring = false;
stopForeground(true);
Log.d(TAG, "Stopped battery monitoring");
}
private void createNotificationChannel() {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"Charging Bypass",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("Charging bypass monitoring service");
channel.setShowBadge(false);
mNotificationManager.createNotificationChannel(channel);
}
private void updateNotification() {
if (!mIsMonitoring) return;
boolean bypassActive = mChargeUtils.isBypassChargeEnabled();
int batteryLevel = mChargeUtils.getCurrentBatteryLevel();
int mode = mChargeUtils.getBypassMode();
// If bypass is not active, hide notification completely regardless of mode
if (!bypassActive) {
stopForeground(true);
mNotificationManager.cancel(NOTIFICATION_ID);
return;
}
String title = getString(R.string.charge_bypass_notification_title);
String text;
if (mode == ChargeUtils.MODE_SMART) {
int target = mChargeUtils.getBatteryTarget();
if (bypassActive) {
text = getString(R.string.charge_bypass_smart_enabled, target);
} else {
text = "Smart mode active - Target: " + target + "% (Current: " + batteryLevel + "%)";
}
} else {
text = getString(R.string.charge_bypass_notification_text);
}
// Create a simple settings intent instead of ChargeActivity
Intent settingsIntent = new Intent(android.provider.Settings.ACTION_SETTINGS);
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, settingsIntent, PendingIntent.FLAG_IMMUTABLE
);
Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(android.R.drawable.ic_menu_manage) // Use system icon as fallback
.setContentIntent(pendingIntent)
.setOngoing(true)
.setShowWhen(false)
.build();
if (bypassActive) {
startForeground(NOTIFICATION_ID, notification);
}
}
private class BatteryReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_BATTERY_CHANGED.equals(action) ||
Intent.ACTION_POWER_CONNECTED.equals(action) ||
Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
Log.d(TAG, "Battery state changed: " + action);
// Check if we should still be running
if (!mChargeUtils.isMasterToggleEnabled()) {
Log.d(TAG, "Master toggle disabled, stopping service");
stopSelf();
return;
}
// Update bypass state based on current conditions
boolean wasActive = mChargeUtils.isBypassChargeEnabled();
mChargeUtils.updateBypassState();
boolean isActive = mChargeUtils.isBypassChargeEnabled();
// Update notification
updateNotification();
// Log state changes
if (wasActive != isActive) {
int batteryLevel = mChargeUtils.getCurrentBatteryLevel();
Log.d(TAG, "Bypass state changed: " + isActive +
" (Battery: " + batteryLevel + "%)");
}
}
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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.charge;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.util.Log;
import org.lineageos.settings.R;
public class ChargeQSTile extends TileService {
private static final String TAG = "ChargeQSTile";
private ChargeUtils mChargeUtils;
@Override
public void onCreate() {
super.onCreate();
mChargeUtils = new ChargeUtils(this);
}
@Override
public void onStartListening() {
super.onStartListening();
updateTile();
}
@Override
public void onStopListening() {
super.onStopListening();
}
@Override
public void onClick() {
super.onClick();
if (!mChargeUtils.canToggleManually()) {
// Show inactive state if not in manual mode or master toggle is off
updateTile();
return;
}
// Toggle manual bypass state
boolean currentState = mChargeUtils.getManualBypassState();
mChargeUtils.setManualBypassState(!currentState);
// Update tile immediately
updateTile();
Log.d(TAG, "Manual bypass toggled to: " + !currentState);
}
private void updateTile() {
Tile tile = getQsTile();
if (tile == null) return;
boolean masterEnabled = mChargeUtils.isMasterToggleEnabled();
boolean isManualMode = mChargeUtils.getBypassMode() == ChargeUtils.MODE_MANUAL;
boolean canToggle = masterEnabled && isManualMode;
boolean bypassActive = mChargeUtils.isBypassChargeEnabled();
// Set tile icon
Icon icon = Icon.createWithResource(this, R.drawable.ic_charge_bypass);
tile.setIcon(icon);
// Set tile label
tile.setLabel(getString(R.string.charge_bypass_qs_tile_label));
if (!masterEnabled) {
// Master toggle is off
tile.setState(Tile.STATE_UNAVAILABLE);
tile.setSubtitle(getString(R.string.charge_bypass_qs_disabled));
} else if (!isManualMode) {
// In smart mode
tile.setState(Tile.STATE_UNAVAILABLE);
tile.setSubtitle("Smart Mode");
} else if (canToggle) {
// Manual mode and can toggle
if (bypassActive) {
tile.setState(Tile.STATE_ACTIVE);
tile.setSubtitle(getString(R.string.charge_bypass_qs_active));
} else {
tile.setState(Tile.STATE_INACTIVE);
tile.setSubtitle(getString(R.string.charge_bypass_qs_inactive));
}
} else {
// Fallback state
tile.setState(Tile.STATE_UNAVAILABLE);
tile.setSubtitle(getString(R.string.charge_bypass_qs_disabled));
}
tile.updateTile();
}
@Override
public void onTileAdded() {
super.onTileAdded();
updateTile();
}
@Override
public void onTileRemoved() {
super.onTileRemoved();
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.charge;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class ChargeReceiver extends BroadcastReceiver {
private static final String TAG = "ChargeReceiver";
@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)) {
Log.d(TAG, "Boot completed, restoring charge bypass settings");
ChargeUtils chargeUtils = new ChargeUtils(context);
// Check if bypass is supported on this device
if (!chargeUtils.isBypassChargeSupported()) {
Log.d(TAG, "Bypass not supported on this device");
return;
}
// Restore bypass state based on saved preferences
if (chargeUtils.isMasterToggleEnabled()) {
Log.d(TAG, "Master toggle enabled, restoring bypass state");
// Reset bypass to disabled state first
chargeUtils.enableBypassCharge(false);
// Start monitoring service if needed
int mode = chargeUtils.getBypassMode();
if (mode == ChargeUtils.MODE_SMART) {
Intent serviceIntent = new Intent(context, ChargeBatteryMonitorService.class);
context.startService(serviceIntent);
Log.d(TAG, "Started battery monitoring service for smart mode");
} else if (mode == ChargeUtils.MODE_MANUAL) {
// In manual mode, respect the saved manual state
// but don't automatically enable bypass on boot for safety
chargeUtils.setManualBypassState(false);
Log.d(TAG, "Manual mode restored, bypass disabled for safety");
}
// Update bypass state based on current conditions
chargeUtils.updateBypassState();
} else {
Log.d(TAG, "Master toggle disabled, ensuring bypass is off");
chargeUtils.enableBypassCharge(false);
}
}
}
}

View File

@@ -0,0 +1,203 @@
/*
* 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.charge;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference;
import org.lineageos.settings.R;
import androidx.preference.PreferenceFragmentCompat;
public class ChargeSettingsFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceChangeListener {
private static final String TAG = "ChargeSettingsFragment";
// Preference keys
private static final String KEY_BYPASS_CHARGE_MASTER = "bypass_charge_master";
private static final String KEY_BYPASS_MODE_MANUAL = "bypass_mode_manual";
private static final String KEY_BYPASS_MODE_SMART = "bypass_mode_smart";
private static final String KEY_BATTERY_TARGET_SLIDER = "battery_target_slider";
private static final String KEY_BYPASS_CONFIGURATION_CATEGORY = "bypass_configuration_category";
// Preferences
private SwitchPreference mMasterToggle;
private CheckBoxPreference mManualMode;
private CheckBoxPreference mSmartMode;
private MinMaxSeekBarPreference mBatteryTarget;
private PreferenceCategory mConfigurationCategory;
private ChargeUtils mChargeUtils;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.charge_settings, rootKey);
mChargeUtils = new ChargeUtils(getActivity());
// Initialize preferences
mMasterToggle = (SwitchPreference) findPreference(KEY_BYPASS_CHARGE_MASTER);
mManualMode = (CheckBoxPreference) findPreference(KEY_BYPASS_MODE_MANUAL);
mSmartMode = (CheckBoxPreference) findPreference(KEY_BYPASS_MODE_SMART);
mBatteryTarget = (MinMaxSeekBarPreference) findPreference(KEY_BATTERY_TARGET_SLIDER);
mConfigurationCategory = (PreferenceCategory) findPreference(KEY_BYPASS_CONFIGURATION_CATEGORY);
boolean bypassSupported = mChargeUtils.isBypassChargeSupported();
if (!bypassSupported) {
// Disable all preferences if bypass is not supported
mMasterToggle.setEnabled(false);
mMasterToggle.setSummary(R.string.charge_bypass_unavailable);
mConfigurationCategory.setEnabled(false);
return;
}
// Set up master toggle
mMasterToggle.setChecked(mChargeUtils.isMasterToggleEnabled());
mMasterToggle.setOnPreferenceChangeListener(this);
// Set up mode preferences
int currentMode = mChargeUtils.getBypassMode();
mManualMode.setChecked(currentMode == ChargeUtils.MODE_MANUAL);
mSmartMode.setChecked(currentMode == ChargeUtils.MODE_SMART);
mManualMode.setOnPreferenceChangeListener(this);
mSmartMode.setOnPreferenceChangeListener(this);
// Set up battery target slider - Let the preference handle all mapping internally
int batteryTarget = mChargeUtils.getBatteryTarget();
mBatteryTarget.setBatteryTarget(batteryTarget); // Use the new method from MinMaxSeekBarPreference
mBatteryTarget.setOnPreferenceChangeListener(this);
updateBatteryTargetSummary(batteryTarget);
Log.d(TAG, "Initial battery target: " + batteryTarget + "%");
// Update UI state based on master toggle
updateConfigurationVisibility(mChargeUtils.isMasterToggleEnabled());
updateBatteryTargetVisibility(currentMode == ChargeUtils.MODE_SMART);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String key = preference.getKey();
switch (key) {
case KEY_BYPASS_CHARGE_MASTER:
boolean masterEnabled = (Boolean) newValue;
if (masterEnabled) {
showMasterToggleWarning(() -> {
mChargeUtils.setMasterToggleEnabled(true);
updateConfigurationVisibility(true);
startBatteryMonitorService();
});
return false; // Don't update UI until user confirms
} else {
mChargeUtils.setMasterToggleEnabled(false);
mChargeUtils.enableBypassCharge(false); // Disable bypass immediately
updateConfigurationVisibility(false);
stopBatteryMonitorService();
return true;
}
case KEY_BYPASS_MODE_MANUAL:
if ((Boolean) newValue) {
mSmartMode.setChecked(false);
mChargeUtils.setBypassMode(ChargeUtils.MODE_MANUAL);
updateBatteryTargetVisibility(false);
stopBatteryMonitorService();
return true;
}
return false; // Don't allow unchecking without selecting another mode
case KEY_BYPASS_MODE_SMART:
if ((Boolean) newValue) {
mManualMode.setChecked(false);
mChargeUtils.setBypassMode(ChargeUtils.MODE_SMART);
updateBatteryTargetVisibility(true);
startBatteryMonitorService();
return true;
}
return false; // Don't allow unchecking without selecting another mode
case KEY_BATTERY_TARGET_SLIDER:
// FIXED: Calculate the battery target based on the new slider value
int sliderValue = (Integer) newValue;
// Create a temporary preference object to calculate the mapped value
// since the preference hasn't been updated yet
int actualBatteryTarget = mBatteryTarget.calculateBatteryTargetFromSlider(sliderValue);
Log.d(TAG, "Slider changed - Position: " + sliderValue + "%, Battery target: " + actualBatteryTarget + "%");
mChargeUtils.setBatteryTarget(actualBatteryTarget);
updateBatteryTargetSummary(actualBatteryTarget);
return true;
}
return false;
}
private void showMasterToggleWarning(Runnable onConfirm) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.charge_bypass_master_title)
.setMessage(R.string.charge_bypass_warning)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
mMasterToggle.setChecked(true);
if (onConfirm != null) {
onConfirm.run();
}
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
mMasterToggle.setChecked(false);
})
.show();
}
private void updateConfigurationVisibility(boolean visible) {
mConfigurationCategory.setVisible(visible);
if (visible) {
int currentMode = mChargeUtils.getBypassMode();
updateBatteryTargetVisibility(currentMode == ChargeUtils.MODE_SMART);
} else {
updateBatteryTargetVisibility(false);
}
}
private void updateBatteryTargetVisibility(boolean visible) {
mBatteryTarget.setVisible(visible);
}
private void updateBatteryTargetSummary(int value) {
String summaryText = getString(R.string.charge_bypass_battery_target_summary, value);
mBatteryTarget.setSummary(summaryText);
}
private void startBatteryMonitorService() {
Intent serviceIntent = new Intent(getActivity(), ChargeBatteryMonitorService.class);
getActivity().startService(serviceIntent);
}
private void stopBatteryMonitorService() {
Intent serviceIntent = new Intent(getActivity(), ChargeBatteryMonitorService.class);
getActivity().stopService(serviceIntent);
}
}

View File

@@ -0,0 +1,239 @@
/*
* 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.charge;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.BatteryManager;
import android.util.Log;
import androidx.preference.PreferenceManager;
import org.lineageos.settings.utils.FileUtils;
public class ChargeUtils {
private static final String TAG = "ChargeUtils";
// Sysfs node for bypass control
public static final String BYPASS_CHARGE_NODE = "/sys/class/power_supply/battery/input_suspend";
// Bypass modes
public static final int MODE_MANUAL = 0;
public static final int MODE_SMART = 1;
// Bypass states
public static final int BYPASS_DISABLED = 0;
public static final int BYPASS_ENABLED = 1;
// SharedPreferences keys
private static final String PREF_MASTER_TOGGLE = "bypass_charge_master";
private static final String PREF_BYPASS_MODE = "bypass_mode";
private static final String PREF_BATTERY_TARGET = "battery_target";
private static final String PREF_MANUAL_BYPASS_STATE = "manual_bypass_state";
// Default values
private static final int DEFAULT_BATTERY_TARGET = 80;
private static final int BATTERY_HYSTERESIS = 5; // 5% hysteresis for smart mode
private Context mContext;
private SharedPreferences mSharedPrefs;
public ChargeUtils(Context context) {
mContext = context;
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
}
// Master toggle methods
public boolean isMasterToggleEnabled() {
return mSharedPrefs.getBoolean(PREF_MASTER_TOGGLE, false);
}
public void setMasterToggleEnabled(boolean enabled) {
mSharedPrefs.edit().putBoolean(PREF_MASTER_TOGGLE, enabled).apply();
if (!enabled) {
// Disable bypass immediately when master toggle is turned off
enableBypassCharge(false);
}
}
// Mode management methods
public int getBypassMode() {
return mSharedPrefs.getInt(PREF_BYPASS_MODE, MODE_MANUAL);
}
public void setBypassMode(int mode) {
mSharedPrefs.edit().putInt(PREF_BYPASS_MODE, mode).apply();
// Reset bypass state when changing modes
if (mode == MODE_MANUAL) {
setManualBypassState(false);
}
// Update bypass state based on new mode
updateBypassState();
}
// Battery target methods (for smart mode)
public int getBatteryTarget() {
return mSharedPrefs.getInt(PREF_BATTERY_TARGET, DEFAULT_BATTERY_TARGET);
}
public void setBatteryTarget(int target) {
mSharedPrefs.edit().putInt(PREF_BATTERY_TARGET, target).apply();
// Immediately update bypass state if we're in smart mode
if (getBypassMode() == MODE_SMART) {
updateBypassState();
}
}
// Manual bypass state methods
public boolean getManualBypassState() {
return mSharedPrefs.getBoolean(PREF_MANUAL_BYPASS_STATE, false);
}
public void setManualBypassState(boolean enabled) {
mSharedPrefs.edit().putBoolean(PREF_MANUAL_BYPASS_STATE, enabled).apply();
if (getBypassMode() == MODE_MANUAL) {
updateBypassState();
}
}
// Core bypass control methods
public boolean isBypassChargeEnabled() {
try {
String value = FileUtils.readOneLine(BYPASS_CHARGE_NODE);
return value != null && value.equals("1");
} catch (Exception e) {
Log.e(TAG, "Failed to read bypass charge status", e);
return false;
}
}
public void enableBypassCharge(boolean enable) {
try {
FileUtils.writeLine(BYPASS_CHARGE_NODE, enable ? "1" : "0");
Log.d(TAG, "Bypass charge " + (enable ? "enabled" : "disabled"));
} catch (Exception e) {
Log.e(TAG, "Failed to write bypass charge status", e);
}
}
// Support check methods
public boolean isNodeAccessible(String node) {
try {
FileUtils.readOneLine(node);
return true;
} catch (Exception e) {
Log.e(TAG, "Node " + node + " not accessible", e);
return false;
}
}
public boolean isBypassChargeSupported() {
return isNodeAccessible(BYPASS_CHARGE_NODE);
}
// Main logic for updating bypass state
public void updateBypassState() {
if (!isMasterToggleEnabled()) {
enableBypassCharge(false);
return;
}
int mode = getBypassMode();
boolean shouldBypass = false;
switch (mode) {
case MODE_MANUAL:
shouldBypass = getManualBypassState();
break;
case MODE_SMART:
shouldBypass = shouldBypassInSmartMode();
break;
}
enableBypassCharge(shouldBypass);
}
// Smart mode logic
private boolean shouldBypassInSmartMode() {
int currentBattery = getCurrentBatteryLevel();
int target = getBatteryTarget();
boolean currentlyBypassed = isBypassChargeEnabled();
if (currentBattery >= target && !currentlyBypassed) {
// Enable bypass when we reach target
return true;
} else if (currentBattery <= (target - BATTERY_HYSTERESIS) && currentlyBypassed) {
// Disable bypass when we drop below target minus hysteresis
return false;
}
// Keep current state if we're between thresholds
return currentlyBypassed;
}
// Battery level helper
public int getCurrentBatteryLevel() {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = mContext.registerReceiver(null, ifilter);
if (batteryStatus != null) {
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
if (level != -1 && scale != -1) {
return (int) ((level / (float) scale) * 100);
}
}
return -1; // Unknown battery level
}
// Charging state helper
public boolean isCharging() {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = mContext.registerReceiver(null, ifilter);
if (batteryStatus != null) {
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
return status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
}
return false;
}
// Utility methods for external components
public String getBypassModeString() {
switch (getBypassMode()) {
case MODE_MANUAL:
return "Manual";
case MODE_SMART:
return "Smart";
default:
return "Unknown";
}
}
public boolean canToggleManually() {
return isMasterToggleEnabled() && getBypassMode() == MODE_MANUAL;
}
}

View File

@@ -0,0 +1,225 @@
package org.lineageos.settings.charginginfo
import org.lineageos.settings.R
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.drawable.Icon
import android.os.BatteryManager
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
import java.io.File
import java.util.Timer
import java.util.TimerTask
class ChargingInfoTileService : TileService() {
private var updateTimer: Timer? = null
private var batteryReceiver: BroadcastReceiver? = null
private var isCharging = false
companion object {
private const val TAG = "ChargingTileService"
private const val UPDATE_INTERVAL = 2000L // Update every 2 seconds
// Battery sysfs paths
private const val CURRENT_NOW_PATH = "/sys/class/power_supply/battery/current_now"
private const val VOLTAGE_NOW_PATH = "/sys/class/power_supply/battery/voltage_now"
private const val STATUS_PATH = "/sys/class/power_supply/battery/status"
}
override fun onStartListening() {
super.onStartListening()
Log.d(TAG, "Tile started listening")
// Register battery receiver
registerBatteryReceiver()
// Start periodic updates
startPeriodicUpdates()
// Initial update
updateTile()
}
override fun onStopListening() {
super.onStopListening()
Log.d(TAG, "Tile stopped listening")
// Stop updates
stopPeriodicUpdates()
// Unregister receiver
unregisterBatteryReceiver()
}
override fun onClick() {
super.onClick()
// Force update when clicked
updateTile()
}
private fun registerBatteryReceiver() {
if (batteryReceiver == null) {
batteryReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_BATTERY_CHANGED,
Intent.ACTION_POWER_CONNECTED,
Intent.ACTION_POWER_DISCONNECTED -> {
updateChargingStatus(intent)
updateTile()
}
}
}
}
val filter = IntentFilter().apply {
addAction(Intent.ACTION_BATTERY_CHANGED)
addAction(Intent.ACTION_POWER_CONNECTED)
addAction(Intent.ACTION_POWER_DISCONNECTED)
}
registerReceiver(batteryReceiver, filter)
}
}
private fun unregisterBatteryReceiver() {
batteryReceiver?.let {
try {
unregisterReceiver(it)
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Receiver not registered")
}
batteryReceiver = null
}
}
private fun startPeriodicUpdates() {
stopPeriodicUpdates()
updateTimer = Timer().apply {
scheduleAtFixedRate(object : TimerTask() {
override fun run() {
if (isCharging) {
updateTile()
}
}
}, 0, UPDATE_INTERVAL)
}
}
private fun stopPeriodicUpdates() {
updateTimer?.cancel()
updateTimer = null
}
private fun updateChargingStatus(intent: Intent) {
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING
}
private fun updateTile() {
val tile = qsTile ?: return
try {
val chargingInfo = getChargingInfo()
tile.apply {
when {
chargingInfo.isCharging -> {
state = Tile.STATE_ACTIVE
label = "Charging"
subtitle = "${chargingInfo.power}W"
contentDescription = "Charging at ${chargingInfo.power}W (${chargingInfo.current}A, ${chargingInfo.voltage}V)"
icon = Icon.createWithResource(this@ChargingInfoTileService,
R.drawable.ic_charging_speed)
}
chargingInfo.isDischarging -> {
state = Tile.STATE_INACTIVE
label = "Discharging"
subtitle = "${chargingInfo.power}W"
contentDescription = "Discharging at ${chargingInfo.power}W"
icon = Icon.createWithResource(this@ChargingInfoTileService,
R.drawable.ic_battery_discharge)
}
else -> {
state = Tile.STATE_UNAVAILABLE
label = "Not Charging"
subtitle = "0W"
contentDescription = "Battery not charging or discharging"
icon = Icon.createWithResource(this@ChargingInfoTileService,
R.drawable.ic_battery_unknown)
}
}
}
tile.updateTile()
} catch (e: Exception) {
Log.e(TAG, "Error updating tile", e)
// Fallback tile state
tile.apply {
state = Tile.STATE_UNAVAILABLE
label = "Error"
subtitle = "N/A"
contentDescription = "Unable to read charging information"
icon = Icon.createWithResource(this@ChargingInfoTileService,
R.drawable.ic_error)
}
tile.updateTile()
}
}
private fun getChargingInfo(): ChargingInfo {
val currentNow = readSysfsValue(CURRENT_NOW_PATH)
val voltageNow = readSysfsValue(VOLTAGE_NOW_PATH)
val status = readSysfsString(STATUS_PATH)
// Convert from milliamps to amps and microvolts to volts
val currentA = currentNow / 1000.0 // mA → A
val voltageV = voltageNow / 1000000.0 // µV → V
// Calculate power in watts
val powerW = Math.abs(currentA * voltageV)
val isCharging = status.equals("Charging", ignoreCase = true) && currentNow > 0
val isDischarging = currentNow < 0
return ChargingInfo(
current = String.format("%.1f", Math.abs(currentA)),
voltage = String.format("%.1f", voltageV),
power = String.format("%.1f", powerW),
isCharging = isCharging,
isDischarging = isDischarging
)
}
private fun readSysfsValue(path: String): Long {
return try {
File(path).readText().trim().toLong()
} catch (e: Exception) {
Log.w(TAG, "Failed to read $path", e)
0L
}
}
private fun readSysfsString(path: String): String {
return try {
File(path).readText().trim()
} catch (e: Exception) {
Log.w(TAG, "Failed to read $path", e)
"Unknown"
}
}
data class ChargingInfo(
val current: String,
val voltage: String,
val power: String,
val isCharging: Boolean,
val isDischarging: Boolean
)
}

View File

@@ -0,0 +1,96 @@
package org.lineageos.settings.charge;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import androidx.preference.SeekBarPreference;
public class MinMaxSeekBarPreference extends SeekBarPreference {
private static final String TAG = "MinMaxSeekBarPreference";
private int mMinValue = 0;
private int mMaxValue = 100;
public MinMaxSeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
// Get min/max values from XML attributes
if (attrs != null) {
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String attrName = attrs.getAttributeName(i);
if ("min".equals(attrName)) {
mMinValue = attrs.getAttributeIntValue(i, 0);
} else if ("max".equals(attrName)) {
mMaxValue = attrs.getAttributeIntValue(i, 100);
}
}
}
// Set the SeekBar to use full 0-100 range for UI, but map values internally
setMin(0);
setMax(100);
Log.d(TAG, "MinMaxSeekBarPreference initialized - Min: " + mMinValue + ", Max: " + mMaxValue);
}
@Override
protected void onSetInitialValue(Object defaultValue) {
int value = defaultValue == null ? 80 : (Integer) defaultValue;
// Ensure value is within bounds
value = Math.max(mMinValue, Math.min(mMaxValue, value));
// Convert the actual battery target value to slider position
int sliderPosition = valueToSliderPosition(value);
super.setValue(sliderPosition);
Log.i(TAG, "Battery target initial value set: " + value + "% (Slider position: " + sliderPosition + "%)");
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
int defaultValue = a.getInt(index, 80);
Log.d(TAG, "Getting default battery target value: " + defaultValue + "%");
return defaultValue;
}
@Override
public void setValue(int seekBarValue) {
// seekBarValue here is the slider position (0-100)
// Just store it as-is since the slider represents the UI position
super.setValue(seekBarValue);
// Calculate and log the actual mapped battery target
int mappedValue = sliderPositionToValue(seekBarValue);
Log.i(TAG, "Battery target changed - Slider: " + seekBarValue + "%, Actual target: " + mappedValue + "% (Range: " + mMinValue + "-" + mMaxValue + "%)");
}
// Convert slider position (0-100) to actual battery target value
private int sliderPositionToValue(int sliderPosition) {
return mMinValue + ((sliderPosition * (mMaxValue - mMinValue)) / 100);
}
// Convert actual battery target value to slider position (0-100)
private int valueToSliderPosition(int value) {
if (mMaxValue == mMinValue) return 0; // Avoid division by zero
return ((value - mMinValue) * 100) / (mMaxValue - mMinValue);
}
// Get the actual mapped battery target value
public int getBatteryTarget() {
int sliderValue = getValue();
return sliderPositionToValue(sliderValue);
}
// Set the battery target value (converts to appropriate slider position)
public void setBatteryTarget(int targetValue) {
targetValue = Math.max(mMinValue, Math.min(mMaxValue, targetValue));
int sliderPosition = valueToSliderPosition(targetValue);
setValue(sliderPosition);
}
// Calculate battery target from a given slider position (used during preference change)
public int calculateBatteryTargetFromSlider(int sliderPosition) {
return sliderPositionToValue(sliderPosition);
}
}

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/thermal/thermal_zone78/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 / 1000f;
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_zone19/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,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;
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.app.StatusBarManager;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.widget.Toast;
import org.lineageos.settings.R;
public class TileUtils {
public static void requestAddTileService(Context context, Class<?> tileServiceClass, int labelResId, int iconResId) {
ComponentName componentName = new ComponentName(context, tileServiceClass);
String label = context.getString(labelResId);
Icon icon = Icon.createWithResource(context, iconResId);
StatusBarManager sbm = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
if (sbm != null) {
sbm.requestAddTileService(
componentName,
label,
icon,
context.getMainExecutor(),
result -> handleResult(context, result)
);
}
}
private static void handleResult(Context context, Integer result) {
if (result == null)
return;
switch (result) {
case StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED:
Toast.makeText(context, R.string.tile_added, Toast.LENGTH_SHORT).show();
break;
case StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED:
Toast.makeText(context, R.string.tile_not_added, Toast.LENGTH_SHORT).show();
break;
case StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED:
Toast.makeText(context, R.string.tile_already_added, Toast.LENGTH_SHORT).show();
break;
}
}
}

View File

@@ -1,6 +1,3 @@
# Charger
ro.charger.enable_suspend=true
# Display
debug.sf.hw=0
debug.egl.hw=0

File diff suppressed because it is too large Load Diff

View File

@@ -122,6 +122,14 @@ on early-boot
exec u:r:vendor_qti_init_shell:s0 -- /vendor/bin/init.qti.can.sh
setprop ro.sf.lcd_density ${vendor.display.lcd_density}
# SSR
write /sys/bus/msm_subsys/devices/subsys0/restart_level RELATED
write /sys/bus/msm_subsys/devices/subsys1/restart_level RELATED
write /sys/bus/msm_subsys/devices/subsys2/restart_level RELATED
write /sys/bus/msm_subsys/devices/subsys3/restart_level RELATED
write /sys/bus/msm_subsys/devices/subsys4/restart_level RELATED
write /sys/bus/msm_subsys/devices/subsys5/restart_level RELATED
chown system system /mnt/vendor/persist/data/pfm/licenses
chmod 0775 /mnt/vendor/persist/data/pfm/licenses
@@ -560,6 +568,11 @@ on property:ro.crypto.state=unsupported
on property:persist.env.fastdorm.enabled=true
setprop persist.radio.data_no_toggle 1
on post-fs-data
# Charge Bypass
chown system system /sys/class/power_supply/battery
chmod 0660 /sys/class/power_supply/battery
service vendor.qrtr-ns /vendor/bin/qrtr-ns -f
class core
user vendor_qrtr

View File

@@ -29,6 +29,12 @@ on boot
chown system system /sys/class/leds/vibrator/vmax_mv
chmod 0644 /sys/class/leds/vibrator/vmax_mv
chown system graphics /sys/class/drm/sde-crtc-0/measured_fps
chmod 0660 /sys/class/drm/sde-crtc-0/measured_fps
chown system system /sys/class/thermal/thermal_zone78/temp
chmod 0660 /sys/class/thermal/thermal_zone78/temp
on property:ro.boot.product.hardware.sku=nfc
start secure_element_hal_service
start vendor.nfc_hal_service

View File

@@ -87,122 +87,6 @@ on init
chown cameraserver cameraserver /dev/cpuset/camera-daemon/tasks
chmod 0660 /dev/cpuset/camera-daemon/tasks
on late-fs
# Start services for bootanim
start servicemanager
start surfaceflinger
start bootanim
on property:sys.boot_completed=1
# Runtime fs tuning
write /sys/block/sda/queue/nr_requests 128
write /sys/block/sda/queue/iostats 0
# Block layer tuning: discard chunk size up to 128MB
# Otherwise, contiguous discards can be merged
write /sys/block/sda/queue/discard_max_bytes 134217728
# Back to default VM settings
write /proc/sys/vm/dirty_expire_centisecs 3000
write /proc/sys/vm/dirty_background_ratio 10
# Setup runtime cpusets
write /dev/cpuset/top-app/cpus 0-7
write /dev/cpuset/foreground/cpus 0-2,5-7
write /dev/cpuset/background/cpus 0-3
write /dev/cpuset/system-background/cpus 0-3
write /dev/cpuset/restricted/cpus 0-3
# Setup runtime schedTune
write /dev/stune/foreground/schedtune.prefer_idle 1
write /dev/stune/foreground/schedtune.prefer_high_cap 0
write /dev/stune/foreground/schedtune.boost 0
write /dev/stune/schedtune.prefer_idle 0
write /dev/stune/schedtune.prefer_high_cap 0
write /dev/stune/schedtune.boost 0
write /dev/stune/top-app/schedtune.prefer_idle 1
write /dev/stune/top-app/schedtune.prefer_high_cap 0
write /dev/stune/top-app/schedtune.boost 10
# Setup runtime blkio
# value for group_idle is us
write /dev/blkio/blkio.weight 1000
write /dev/blkio/background/blkio.weight 200
write /dev/blkio/blkio.group_idle 2000
write /dev/blkio/background/blkio.group_idle 0
on early-init
# configure governor settings
write /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor schedutil
write /sys/devices/system/cpu/cpu0/cpufreq/schedutil/up_rate_limit_us 500
write /sys/devices/system/cpu/cpu0/cpufreq/schedutil/down_rate_limit_us 20000
# configure governor settings for big cluster
write /sys/devices/system/cpu/cpu4/cpufreq/scaling_governor schedutil
write /sys/devices/system/cpu/cpu4/cpufreq/schedutil/up_rate_limit_us 500
write /sys/devices/system/cpu/cpu4/cpufreq/schedutil/down_rate_limit_us 20000
# Disable UFS clock scaling
write /sys/bus/platform/devices/${ro.boot.bootdevice}/clkscale_enable 0
on init
# Boot time cpuset and stune
write /dev/cpuset/top-app/cpus 0-7
write /dev/cpuset/foreground/cpus 0-7
write /dev/cpuset/background/cpus 0-5
write /dev/cpuset/system-background/cpus 0-5
write /dev/cpuset/restricted/cpus 2-5
write /dev/stune/foreground/schedtune.prefer_idle 1
write /dev/stune/foreground/schedtune.prefer_high_cap 1
write /dev/stune/foreground/schedtune.boost 100
write /dev/stune/schedtune.prefer_idle 1
write /dev/stune/schedtune.prefer_high_cap 1
write /dev/stune/schedtune.boost 100
write /dev/stune/top-app/schedtune.prefer_idle 1
write /dev/stune/top-app/schedtune.prefer_high_cap 1
write /dev/stune/top-app/schedtune.boost 100
# Boot time fs tuning
write /sys/block/sda/queue/iostats 0
write /sys/block/sda/queue/scheduler cfq
write /sys/block/sda/queue/iosched/slice_idle 0
write /sys/block/sda/queue/nr_requests 256
# Disable UFS powersaving
write /sys/devices/platform/soc/${ro.boot.bootdevice}/clkgate_enable 0
# Disable powersaving
write /sys/module/lpm_levels/parameters/sleep_disabled 1
# bring back all cores
write /sys/devices/system/cpu/cpu0/online 1
write /sys/devices/system/cpu/cpu1/online 1
write /sys/devices/system/cpu/cpu2/online 1
write /sys/devices/system/cpu/cpu3/online 1
write /sys/devices/system/cpu/cpu4/online 1
write /sys/devices/system/cpu/cpu5/online 1
write /sys/devices/system/cpu/cpu6/online 1
write /sys/devices/system/cpu/cpu7/online 1
mkdir /dev/cpuset/camera-daemon-dedicated
write /dev/cpuset/camera-daemon-dedicated/cpus 0-7
write /dev/cpuset/camera-daemon-dedicated/mems 0
chown system system /dev/cpuset/camera-daemon-dedicated
chown system system /dev/cpuset/camera-daemon-dedicated/tasks
chmod 0664 /dev/cpuset/camera-daemon-dedicated/tasks
# create an stune group for camera-specific processes
mkdir /dev/stune/camera-daemon
write /dev/stune/camera-daemon/schedtune.prefer_idle 1
write /dev/stune/camera-daemon/schedtune.boost 0
chown system system /dev/stune/camera-daemon
chown system system /dev/stune/camera-daemon/tasks
chmod 0664 /dev/stune/camera-daemon/tasks
# set default schedTune value for camera-daemon
write /dev/stune/camera-daemon/schedtune.prefer_idle 1
write /dev/stune/camera-daemon/schedtune.boost 0
on early-fs
start vold

View File

@@ -1,50 +0,0 @@
//
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
cc_binary {
name: "android.hardware.sensors@2.0-service.multihal_realme_sm7125",
stem: "android.hardware.sensors@2.0-service.multihal",
defaults: [
"hidl_defaults",
],
vendor: true,
relative_install_path: "hw",
srcs: [
"HalProxy.cpp",
"HalProxyCallback.cpp",
"service.cpp",
],
init_rc: ["android.hardware.sensors@2.0-service-multihal.rc"],
vintf_fragments: ["android.hardware.sensors@2.0-multihal.xml"],
header_libs: [
"android.hardware.sensors@2.X-multihal.header",
"android.hardware.sensors@2.X-shared-utils",
],
shared_libs: [
"android.hardware.sensors@2.0",
"android.hardware.sensors@2.0-ScopedWakelock",
"android.hardware.sensors@2.1",
"libbase",
"libcutils",
"libfmq",
"libhidlbase",
"liblog",
"libpower",
"libutils",
],
static_libs: [
"android.hardware.sensors@1.0-convert",
],
}

View File

@@ -1,769 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "HalProxy.h"
#include <android/hardware/sensors/2.0/types.h>
#include <android-base/file.h>
#include "hardware_legacy/power.h"
#include <dlfcn.h>
#include <cinttypes>
#include <cmath>
#include <fstream>
#include <functional>
#include <thread>
namespace android {
namespace hardware {
namespace sensors {
namespace V2_1 {
namespace implementation {
using ::android::hardware::sensors::V1_0::Result;
using ::android::hardware::sensors::V2_0::EventQueueFlagBits;
using ::android::hardware::sensors::V2_0::WakeLockQueueFlagBits;
using ::android::hardware::sensors::V2_0::implementation::getTimeNow;
using ::android::hardware::sensors::V2_0::implementation::kWakelockTimeoutNs;
typedef V2_0::implementation::ISensorsSubHal*(SensorsHalGetSubHalFunc)(uint32_t*);
typedef V2_1::implementation::ISensorsSubHal*(SensorsHalGetSubHalV2_1Func)(uint32_t*);
static constexpr int32_t kBitsAfterSubHalIndex = 24;
/**
* Set the subhal index as first byte of sensor handle and return this modified version.
*
* @param sensorHandle The sensor handle to modify.
* @param subHalIndex The index in the hal proxy of the sub hal this sensor belongs to.
*
* @return The modified sensor handle.
*/
int32_t setSubHalIndex(int32_t sensorHandle, size_t subHalIndex) {
return sensorHandle | (static_cast<int32_t>(subHalIndex) << kBitsAfterSubHalIndex);
}
/**
* Extract the subHalIndex from sensorHandle.
*
* @param sensorHandle The sensorHandle to extract from.
*
* @return The subhal index.
*/
size_t extractSubHalIndex(int32_t sensorHandle) {
return static_cast<size_t>(sensorHandle >> kBitsAfterSubHalIndex);
}
/**
* Convert nanoseconds to milliseconds.
*
* @param nanos The nanoseconds input.
*
* @return The milliseconds count.
*/
int64_t msFromNs(int64_t nanos) {
constexpr int64_t nanosecondsInAMillsecond = 1000000;
return nanos / nanosecondsInAMillsecond;
}
HalProxy::HalProxy() {
const char* kMultiHalConfigFile = "/vendor/etc/sensors/hals.conf";
initializeSubHalListFromConfigFile(kMultiHalConfigFile);
init();
}
HalProxy::HalProxy(std::vector<ISensorsSubHalV2_0*>& subHalList) {
for (ISensorsSubHalV2_0* subHal : subHalList) {
mSubHalList.push_back(std::make_unique<SubHalWrapperV2_0>(subHal));
}
init();
}
HalProxy::HalProxy(std::vector<ISensorsSubHalV2_0*>& subHalList,
std::vector<ISensorsSubHalV2_1*>& subHalListV2_1) {
for (ISensorsSubHalV2_0* subHal : subHalList) {
mSubHalList.push_back(std::make_unique<SubHalWrapperV2_0>(subHal));
}
for (ISensorsSubHalV2_1* subHal : subHalListV2_1) {
mSubHalList.push_back(std::make_unique<SubHalWrapperV2_1>(subHal));
}
init();
}
HalProxy::~HalProxy() {
stopThreads();
}
Return<void> HalProxy::getSensorsList_2_1(ISensorsV2_1::getSensorsList_2_1_cb _hidl_cb) {
std::vector<V2_1::SensorInfo> sensors;
for (const auto& iter : mSensors) {
sensors.push_back(iter.second);
}
_hidl_cb(sensors);
return Void();
}
Return<void> HalProxy::getSensorsList(ISensorsV2_0::getSensorsList_cb _hidl_cb) {
std::vector<V1_0::SensorInfo> sensors;
for (const auto& iter : mSensors) {
sensors.push_back(convertToOldSensorInfo(iter.second));
}
_hidl_cb(sensors);
return Void();
}
Return<Result> HalProxy::setOperationMode(OperationMode mode) {
Result result = Result::OK;
size_t subHalIndex;
for (subHalIndex = 0; subHalIndex < mSubHalList.size(); subHalIndex++) {
result = mSubHalList[subHalIndex]->setOperationMode(mode);
if (result != Result::OK) {
ALOGE("setOperationMode failed for SubHal: %s",
mSubHalList[subHalIndex]->getName().c_str());
break;
}
}
if (result != Result::OK) {
// Reset the subhal operation modes that have been flipped
for (size_t i = 0; i < subHalIndex; i++) {
mSubHalList[i]->setOperationMode(mCurrentOperationMode);
}
} else {
mCurrentOperationMode = mode;
}
return result;
}
Return<Result> HalProxy::activate(int32_t sensorHandle, bool enabled) {
if (!isSubHalIndexValid(sensorHandle)) {
return Result::BAD_VALUE;
}
return getSubHalForSensorHandle(sensorHandle)
->activate(clearSubHalIndex(sensorHandle), enabled);
}
Return<Result> HalProxy::initialize_2_1(
const ::android::hardware::MQDescriptorSync<V2_1::Event>& eventQueueDescriptor,
const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
const sp<V2_1::ISensorsCallback>& sensorsCallback) {
sp<ISensorsCallbackWrapperBase> dynamicCallback =
new ISensorsCallbackWrapperV2_1(sensorsCallback);
// Create the Event FMQ from the eventQueueDescriptor. Reset the read/write positions.
auto eventQueue =
std::make_unique<EventMessageQueueV2_1>(eventQueueDescriptor, true /* resetPointers */);
std::unique_ptr<EventMessageQueueWrapperBase> queue =
std::make_unique<EventMessageQueueWrapperV2_1>(eventQueue);
// Create the Wake Lock FMQ from the wakeLockDescriptor. Reset the read/write positions.
auto hidlWakeLockQueue =
std::make_unique<WakeLockMessageQueue>(wakeLockDescriptor, true /* resetPointers */);
std::unique_ptr<WakeLockMessageQueueWrapperBase> wakeLockQueue =
std::make_unique<WakeLockMessageQueueWrapperHidl>(hidlWakeLockQueue);
return initializeCommon(queue, wakeLockQueue, dynamicCallback);
}
Return<Result> HalProxy::initialize(
const ::android::hardware::MQDescriptorSync<V1_0::Event>& eventQueueDescriptor,
const ::android::hardware::MQDescriptorSync<uint32_t>& wakeLockDescriptor,
const sp<V2_0::ISensorsCallback>& sensorsCallback) {
sp<ISensorsCallbackWrapperBase> dynamicCallback =
new ISensorsCallbackWrapperV2_0(sensorsCallback);
// Create the Event FMQ from the eventQueueDescriptor. Reset the read/write positions.
auto eventQueue =
std::make_unique<EventMessageQueueV2_0>(eventQueueDescriptor, true /* resetPointers */);
std::unique_ptr<EventMessageQueueWrapperBase> queue =
std::make_unique<EventMessageQueueWrapperV1_0>(eventQueue);
// Create the Wake Lock FMQ from the wakeLockDescriptor. Reset the read/write positions.
auto hidlWakeLockQueue =
std::make_unique<WakeLockMessageQueue>(wakeLockDescriptor, true /* resetPointers */);
std::unique_ptr<WakeLockMessageQueueWrapperBase> wakeLockQueue =
std::make_unique<WakeLockMessageQueueWrapperHidl>(hidlWakeLockQueue);
return initializeCommon(queue, wakeLockQueue, dynamicCallback);
}
Return<Result> HalProxy::initializeCommon(
std::unique_ptr<EventMessageQueueWrapperBase>& eventQueue,
std::unique_ptr<WakeLockMessageQueueWrapperBase>& wakeLockQueue,
const sp<ISensorsCallbackWrapperBase>& sensorsCallback) {
Result result = Result::OK;
stopThreads();
resetSharedWakelock();
// So that the pending write events queue can be cleared safely and when we start threads
// again we do not get new events until after initialize resets the subhals.
disableAllSensors();
// Clears the queue if any events were pending write before.
mPendingWriteEventsQueue = std::queue<std::pair<std::vector<V2_1::Event>, size_t>>();
mSizePendingWriteEventsQueue = 0;
// Clears previously connected dynamic sensors
mDynamicSensors.clear();
mDynamicSensorsCallback = sensorsCallback;
// Create the Event FMQ from the eventQueueDescriptor. Reset the read/write positions.
mEventQueue = std::move(eventQueue);
// Create the Wake Lock FMQ that is used by the framework to communicate whenever WAKE_UP
// events have been successfully read and handled by the framework.
mWakeLockQueue = std::move(wakeLockQueue);
if (mEventQueueFlag != nullptr) {
EventFlag::deleteEventFlag(&mEventQueueFlag);
}
if (mWakelockQueueFlag != nullptr) {
EventFlag::deleteEventFlag(&mWakelockQueueFlag);
}
if (EventFlag::createEventFlag(mEventQueue->getEventFlagWord(), &mEventQueueFlag) != OK) {
result = Result::BAD_VALUE;
}
if (EventFlag::createEventFlag(mWakeLockQueue->getEventFlagWord(), &mWakelockQueueFlag) != OK) {
result = Result::BAD_VALUE;
}
if (!mDynamicSensorsCallback || !mEventQueue || !mWakeLockQueue || mEventQueueFlag == nullptr) {
result = Result::BAD_VALUE;
}
mThreadsRun.store(true);
mPendingWritesThread = std::thread(startPendingWritesThread, this);
mWakelockThread = std::thread(startWakelockThread, this);
for (size_t i = 0; i < mSubHalList.size(); i++) {
Result currRes = mSubHalList[i]->initialize(this, this, i);
if (currRes != Result::OK) {
result = currRes;
ALOGE("Subhal '%s' failed to initialize.", mSubHalList[i]->getName().c_str());
break;
}
}
mCurrentOperationMode = OperationMode::NORMAL;
return result;
}
Return<Result> HalProxy::batch(int32_t sensorHandle, int64_t samplingPeriodNs,
int64_t maxReportLatencyNs) {
if (!isSubHalIndexValid(sensorHandle)) {
return Result::BAD_VALUE;
}
return getSubHalForSensorHandle(sensorHandle)
->batch(clearSubHalIndex(sensorHandle), samplingPeriodNs, maxReportLatencyNs);
}
Return<Result> HalProxy::flush(int32_t sensorHandle) {
if (!isSubHalIndexValid(sensorHandle)) {
return Result::BAD_VALUE;
}
return getSubHalForSensorHandle(sensorHandle)->flush(clearSubHalIndex(sensorHandle));
}
Return<Result> HalProxy::injectSensorData_2_1(const V2_1::Event& event) {
return injectSensorData(convertToOldEvent(event));
}
Return<Result> HalProxy::injectSensorData(const V1_0::Event& event) {
Result result = Result::OK;
if (mCurrentOperationMode == OperationMode::NORMAL &&
event.sensorType != V1_0::SensorType::ADDITIONAL_INFO) {
ALOGE("An event with type != ADDITIONAL_INFO passed to injectSensorData while operation"
" mode was NORMAL.");
result = Result::BAD_VALUE;
}
if (result == Result::OK) {
V1_0::Event subHalEvent = event;
if (!isSubHalIndexValid(event.sensorHandle)) {
return Result::BAD_VALUE;
}
subHalEvent.sensorHandle = clearSubHalIndex(event.sensorHandle);
result = getSubHalForSensorHandle(event.sensorHandle)
->injectSensorData(convertToNewEvent(subHalEvent));
}
return result;
}
Return<void> HalProxy::registerDirectChannel(const SharedMemInfo& mem,
ISensorsV2_0::registerDirectChannel_cb _hidl_cb) {
if (mDirectChannelSubHal == nullptr) {
_hidl_cb(Result::INVALID_OPERATION, -1 /* channelHandle */);
} else {
mDirectChannelSubHal->registerDirectChannel(mem, _hidl_cb);
}
return Return<void>();
}
Return<Result> HalProxy::unregisterDirectChannel(int32_t channelHandle) {
Result result;
if (mDirectChannelSubHal == nullptr) {
result = Result::INVALID_OPERATION;
} else {
result = mDirectChannelSubHal->unregisterDirectChannel(channelHandle);
}
return result;
}
Return<void> HalProxy::configDirectReport(int32_t sensorHandle, int32_t channelHandle,
RateLevel rate,
ISensorsV2_0::configDirectReport_cb _hidl_cb) {
if (mDirectChannelSubHal == nullptr) {
_hidl_cb(Result::INVALID_OPERATION, -1 /* reportToken */);
} else if (sensorHandle == -1 && rate != RateLevel::STOP) {
_hidl_cb(Result::BAD_VALUE, -1 /* reportToken */);
} else {
// -1 denotes all sensors should be disabled
if (sensorHandle != -1) {
sensorHandle = clearSubHalIndex(sensorHandle);
}
mDirectChannelSubHal->configDirectReport(sensorHandle, channelHandle, rate, _hidl_cb);
}
return Return<void>();
}
Return<void> HalProxy::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& /*args*/) {
if (fd.getNativeHandle() == nullptr || fd->numFds < 1) {
ALOGE("%s: missing fd for writing", __FUNCTION__);
return Void();
}
android::base::borrowed_fd writeFd = dup(fd->data[0]);
std::ostringstream stream;
stream << "===HalProxy===" << std::endl;
stream << "Internal values:" << std::endl;
stream << " Threads are running: " << (mThreadsRun.load() ? "true" : "false") << std::endl;
int64_t now = getTimeNow();
stream << " Wakelock timeout start time: " << msFromNs(now - mWakelockTimeoutStartTime)
<< " ms ago" << std::endl;
stream << " Wakelock timeout reset time: " << msFromNs(now - mWakelockTimeoutResetTime)
<< " ms ago" << std::endl;
// TODO(b/142969448): Add logging for history of wakelock acquisition per subhal.
stream << " Wakelock ref count: " << mWakelockRefCount << std::endl;
stream << " # of events on pending write writes queue: " << mSizePendingWriteEventsQueue
<< std::endl;
stream << " Most events seen on pending write events queue: "
<< mMostEventsObservedPendingWriteEventsQueue << std::endl;
if (!mPendingWriteEventsQueue.empty()) {
stream << " Size of events list on front of pending writes queue: "
<< mPendingWriteEventsQueue.front().first.size() << std::endl;
}
stream << " # of non-dynamic sensors across all subhals: " << mSensors.size() << std::endl;
stream << " # of dynamic sensors across all subhals: " << mDynamicSensors.size() << std::endl;
stream << "SubHals (" << mSubHalList.size() << "):" << std::endl;
for (auto& subHal : mSubHalList) {
stream << " Name: " << subHal->getName() << std::endl;
stream << " Debug dump: " << std::endl;
android::base::WriteStringToFd(stream.str(), writeFd);
subHal->debug(fd, {});
stream.str("");
stream << std::endl;
}
android::base::WriteStringToFd(stream.str(), writeFd);
return Return<void>();
}
Return<void> HalProxy::onDynamicSensorsConnected(const hidl_vec<SensorInfo>& dynamicSensorsAdded,
int32_t subHalIndex) {
std::vector<SensorInfo> sensors;
{
std::lock_guard<std::mutex> lock(mDynamicSensorsMutex);
for (SensorInfo sensor : dynamicSensorsAdded) {
if (!subHalIndexIsClear(sensor.sensorHandle)) {
ALOGE("Dynamic sensor added %s had sensorHandle with first byte not 0.",
sensor.name.c_str());
} else {
sensor.sensorHandle = setSubHalIndex(sensor.sensorHandle, subHalIndex);
mDynamicSensors[sensor.sensorHandle] = sensor;
sensors.push_back(sensor);
}
}
}
mDynamicSensorsCallback->onDynamicSensorsConnected(sensors);
return Return<void>();
}
Return<void> HalProxy::onDynamicSensorsDisconnected(
const hidl_vec<int32_t>& dynamicSensorHandlesRemoved, int32_t subHalIndex) {
// TODO(b/143302327): Block this call until all pending events are flushed from queue
std::vector<int32_t> sensorHandles;
{
std::lock_guard<std::mutex> lock(mDynamicSensorsMutex);
for (int32_t sensorHandle : dynamicSensorHandlesRemoved) {
if (!subHalIndexIsClear(sensorHandle)) {
ALOGE("Dynamic sensorHandle removed had first byte not 0.");
} else {
sensorHandle = setSubHalIndex(sensorHandle, subHalIndex);
if (mDynamicSensors.find(sensorHandle) != mDynamicSensors.end()) {
mDynamicSensors.erase(sensorHandle);
sensorHandles.push_back(sensorHandle);
}
}
}
}
mDynamicSensorsCallback->onDynamicSensorsDisconnected(sensorHandles);
return Return<void>();
}
void HalProxy::initializeSubHalListFromConfigFile(const char* configFileName) {
std::ifstream subHalConfigStream(configFileName);
if (!subHalConfigStream) {
ALOGE("Failed to load subHal config file: %s", configFileName);
} else {
std::string subHalLibraryFile;
while (subHalConfigStream >> subHalLibraryFile) {
void* handle = getHandleForSubHalSharedObject(subHalLibraryFile);
if (handle == nullptr) {
ALOGE("dlopen failed for library: %s", subHalLibraryFile.c_str());
} else {
SensorsHalGetSubHalFunc* sensorsHalGetSubHalPtr =
(SensorsHalGetSubHalFunc*)dlsym(handle, "sensorsHalGetSubHal");
if (sensorsHalGetSubHalPtr != nullptr) {
std::function<SensorsHalGetSubHalFunc> sensorsHalGetSubHal =
*sensorsHalGetSubHalPtr;
uint32_t version;
ISensorsSubHalV2_0* subHal = sensorsHalGetSubHal(&version);
if (version != SUB_HAL_2_0_VERSION) {
ALOGE("SubHal version was not 2.0 for library: %s",
subHalLibraryFile.c_str());
} else {
ALOGV("Loaded SubHal from library: %s", subHalLibraryFile.c_str());
mSubHalList.push_back(std::make_unique<SubHalWrapperV2_0>(subHal));
}
} else {
SensorsHalGetSubHalV2_1Func* getSubHalV2_1Ptr =
(SensorsHalGetSubHalV2_1Func*)dlsym(handle, "sensorsHalGetSubHal_2_1");
if (getSubHalV2_1Ptr == nullptr) {
ALOGE("Failed to locate sensorsHalGetSubHal function for library: %s",
subHalLibraryFile.c_str());
} else {
std::function<SensorsHalGetSubHalV2_1Func> sensorsHalGetSubHal_2_1 =
*getSubHalV2_1Ptr;
uint32_t version;
ISensorsSubHalV2_1* subHal = sensorsHalGetSubHal_2_1(&version);
if (version != SUB_HAL_2_1_VERSION) {
ALOGE("SubHal version was not 2.1 for library: %s",
subHalLibraryFile.c_str());
} else {
ALOGV("Loaded SubHal from library: %s", subHalLibraryFile.c_str());
mSubHalList.push_back(std::make_unique<SubHalWrapperV2_1>(subHal));
}
}
}
}
}
}
}
void HalProxy::initializeSensorList() {
for (size_t subHalIndex = 0; subHalIndex < mSubHalList.size(); subHalIndex++) {
auto result = mSubHalList[subHalIndex]->getSensorsList([&](const auto& list) {
for (SensorInfo sensor : list) {
if (!subHalIndexIsClear(sensor.sensorHandle)) {
ALOGE("SubHal sensorHandle's first byte was not 0");
} else {
ALOGV("Loaded sensor: %s", sensor.name.c_str());
sensor.sensorHandle = setSubHalIndex(sensor.sensorHandle, subHalIndex);
setDirectChannelFlags(&sensor, mSubHalList[subHalIndex]);
if (sensor.typeAsString == "qti.sensor.wise_light") {
sensor.type = SensorType::LIGHT;
sensor.typeAsString = "";
}
mSensors[sensor.sensorHandle] = sensor;
}
}
});
if (!result.isOk()) {
ALOGE("getSensorsList call failed for SubHal: %s",
mSubHalList[subHalIndex]->getName().c_str());
}
}
}
void* HalProxy::getHandleForSubHalSharedObject(const std::string& filename) {
static const std::string kSubHalShareObjectLocations[] = {
"", // Default locations will be searched
#ifdef __LP64__
"/vendor/lib64/hw/", "/odm/lib64/hw/"
#else
"/vendor/lib/hw/", "/odm/lib/hw/"
#endif
};
for (const std::string& dir : kSubHalShareObjectLocations) {
void* handle = dlopen((dir + filename).c_str(), RTLD_NOW);
if (handle != nullptr) {
return handle;
}
}
return nullptr;
}
void HalProxy::init() {
initializeSensorList();
}
void HalProxy::stopThreads() {
mThreadsRun.store(false);
if (mEventQueueFlag != nullptr && mEventQueue != nullptr) {
size_t numToRead = mEventQueue->availableToRead();
std::vector<Event> events(numToRead);
mEventQueue->read(events.data(), numToRead);
mEventQueueFlag->wake(static_cast<uint32_t>(EventQueueFlagBits::EVENTS_READ));
}
if (mWakelockQueueFlag != nullptr && mWakeLockQueue != nullptr) {
uint32_t kZero = 0;
mWakeLockQueue->write(&kZero);
mWakelockQueueFlag->wake(static_cast<uint32_t>(WakeLockQueueFlagBits::DATA_WRITTEN));
}
mWakelockCV.notify_one();
mEventQueueWriteCV.notify_one();
if (mPendingWritesThread.joinable()) {
mPendingWritesThread.join();
}
if (mWakelockThread.joinable()) {
mWakelockThread.join();
}
}
void HalProxy::disableAllSensors() {
for (const auto& sensorEntry : mSensors) {
int32_t sensorHandle = sensorEntry.first;
activate(sensorHandle, false /* enabled */);
}
std::lock_guard<std::mutex> dynamicSensorsLock(mDynamicSensorsMutex);
for (const auto& sensorEntry : mDynamicSensors) {
int32_t sensorHandle = sensorEntry.first;
activate(sensorHandle, false /* enabled */);
}
}
void HalProxy::startPendingWritesThread(HalProxy* halProxy) {
halProxy->handlePendingWrites();
}
void HalProxy::handlePendingWrites() {
// TODO(b/143302327): Find a way to optimize locking strategy maybe using two mutexes instead of
// one.
std::unique_lock<std::mutex> lock(mEventQueueWriteMutex);
while (mThreadsRun.load()) {
mEventQueueWriteCV.wait(
lock, [&] { return !mPendingWriteEventsQueue.empty() || !mThreadsRun.load(); });
if (mThreadsRun.load()) {
std::vector<Event>& pendingWriteEvents = mPendingWriteEventsQueue.front().first;
size_t numWakeupEvents = mPendingWriteEventsQueue.front().second;
size_t eventQueueSize = mEventQueue->getQuantumCount();
size_t numToWrite = std::min(pendingWriteEvents.size(), eventQueueSize);
lock.unlock();
if (!mEventQueue->writeBlocking(
pendingWriteEvents.data(), numToWrite,
static_cast<uint32_t>(EventQueueFlagBits::EVENTS_READ),
static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS),
kPendingWriteTimeoutNs, mEventQueueFlag)) {
ALOGE("Dropping %zu events after blockingWrite failed.", numToWrite);
if (numWakeupEvents > 0) {
if (pendingWriteEvents.size() > eventQueueSize) {
decrementRefCountAndMaybeReleaseWakelock(
countNumWakeupEvents(pendingWriteEvents, eventQueueSize));
} else {
decrementRefCountAndMaybeReleaseWakelock(numWakeupEvents);
}
}
}
lock.lock();
mSizePendingWriteEventsQueue -= numToWrite;
if (pendingWriteEvents.size() > eventQueueSize) {
// TODO(b/143302327): Check if this erase operation is too inefficient. It will copy
// all the events ahead of it down to fill gap off array at front after the erase.
pendingWriteEvents.erase(pendingWriteEvents.begin(),
pendingWriteEvents.begin() + eventQueueSize);
} else {
mPendingWriteEventsQueue.pop();
}
}
}
}
void HalProxy::startWakelockThread(HalProxy* halProxy) {
halProxy->handleWakelocks();
}
void HalProxy::handleWakelocks() {
std::unique_lock<std::recursive_mutex> lock(mWakelockMutex);
while (mThreadsRun.load()) {
mWakelockCV.wait(lock, [&] { return mWakelockRefCount > 0 || !mThreadsRun.load(); });
if (mThreadsRun.load()) {
int64_t timeLeft;
if (sharedWakelockDidTimeout(&timeLeft)) {
resetSharedWakelock();
} else {
uint32_t numWakeLocksProcessed;
lock.unlock();
bool success = mWakeLockQueue->readBlocking(
&numWakeLocksProcessed, 1, 0,
static_cast<uint32_t>(WakeLockQueueFlagBits::DATA_WRITTEN), timeLeft);
lock.lock();
if (success) {
decrementRefCountAndMaybeReleaseWakelock(
static_cast<size_t>(numWakeLocksProcessed));
}
}
}
}
resetSharedWakelock();
}
bool HalProxy::sharedWakelockDidTimeout(int64_t* timeLeft) {
bool didTimeout;
int64_t duration = getTimeNow() - mWakelockTimeoutStartTime;
if (duration > kWakelockTimeoutNs) {
didTimeout = true;
} else {
didTimeout = false;
*timeLeft = kWakelockTimeoutNs - duration;
}
return didTimeout;
}
void HalProxy::resetSharedWakelock() {
std::lock_guard<std::recursive_mutex> lockGuard(mWakelockMutex);
decrementRefCountAndMaybeReleaseWakelock(mWakelockRefCount);
mWakelockTimeoutResetTime = getTimeNow();
}
void HalProxy::postEventsToMessageQueue(const std::vector<Event>& events, size_t numWakeupEvents,
V2_0::implementation::ScopedWakelock wakelock) {
size_t numToWrite = 0;
std::lock_guard<std::mutex> lock(mEventQueueWriteMutex);
if (wakelock.isLocked()) {
incrementRefCountAndMaybeAcquireWakelock(numWakeupEvents);
}
if (mPendingWriteEventsQueue.empty()) {
numToWrite = std::min(events.size(), mEventQueue->availableToWrite());
if (numToWrite > 0) {
if (mEventQueue->write(events.data(), numToWrite)) {
// TODO(b/143302327): While loop if mEventQueue->avaiableToWrite > 0 to possibly fit
// in more writes immediately
mEventQueueFlag->wake(static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS));
} else {
numToWrite = 0;
}
}
}
size_t numLeft = events.size() - numToWrite;
if (numToWrite < events.size() &&
mSizePendingWriteEventsQueue + numLeft <= kMaxSizePendingWriteEventsQueue) {
std::vector<Event> eventsLeft(events.begin() + numToWrite, events.end());
mPendingWriteEventsQueue.push({eventsLeft, numWakeupEvents});
mSizePendingWriteEventsQueue += numLeft;
mMostEventsObservedPendingWriteEventsQueue =
std::max(mMostEventsObservedPendingWriteEventsQueue, mSizePendingWriteEventsQueue);
mEventQueueWriteCV.notify_one();
}
}
bool HalProxy::incrementRefCountAndMaybeAcquireWakelock(size_t delta,
int64_t* timeoutStart /* = nullptr */) {
if (!mThreadsRun.load()) return false;
std::lock_guard<std::recursive_mutex> lockGuard(mWakelockMutex);
if (mWakelockRefCount == 0) {
acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakelockName);
mWakelockCV.notify_one();
}
mWakelockTimeoutStartTime = getTimeNow();
mWakelockRefCount += delta;
if (timeoutStart != nullptr) {
*timeoutStart = mWakelockTimeoutStartTime;
}
return true;
}
void HalProxy::decrementRefCountAndMaybeReleaseWakelock(size_t delta,
int64_t timeoutStart /* = -1 */) {
if (!mThreadsRun.load()) return;
std::lock_guard<std::recursive_mutex> lockGuard(mWakelockMutex);
if (delta > mWakelockRefCount) {
ALOGE("Decrementing wakelock ref count by %zu when count is %zu",
delta, mWakelockRefCount);
}
if (timeoutStart == -1) timeoutStart = mWakelockTimeoutResetTime;
if (mWakelockRefCount == 0 || timeoutStart < mWakelockTimeoutResetTime) return;
mWakelockRefCount -= std::min(mWakelockRefCount, delta);
if (mWakelockRefCount == 0) {
release_wake_lock(kWakelockName);
}
}
void HalProxy::setDirectChannelFlags(SensorInfo* sensorInfo,
std::shared_ptr<ISubHalWrapperBase> subHal) {
bool sensorSupportsDirectChannel =
(sensorInfo->flags & (V1_0::SensorFlagBits::MASK_DIRECT_REPORT |
V1_0::SensorFlagBits::MASK_DIRECT_CHANNEL)) != 0;
if (mDirectChannelSubHal == nullptr && sensorSupportsDirectChannel) {
mDirectChannelSubHal = subHal;
} else if (mDirectChannelSubHal != nullptr && subHal != mDirectChannelSubHal) {
// disable direct channel capability for sensors in subHals that are not
// the only one we will enable
sensorInfo->flags &= ~(V1_0::SensorFlagBits::MASK_DIRECT_REPORT |
V1_0::SensorFlagBits::MASK_DIRECT_CHANNEL);
}
}
std::shared_ptr<ISubHalWrapperBase> HalProxy::getSubHalForSensorHandle(int32_t sensorHandle) {
return mSubHalList[extractSubHalIndex(sensorHandle)];
}
bool HalProxy::isSubHalIndexValid(int32_t sensorHandle) {
return extractSubHalIndex(sensorHandle) < mSubHalList.size();
}
size_t HalProxy::countNumWakeupEvents(const std::vector<Event>& events, size_t n) {
size_t numWakeupEvents = 0;
for (size_t i = 0; i < n; i++) {
int32_t sensorHandle = events[i].sensorHandle;
if (mSensors[sensorHandle].flags & static_cast<uint32_t>(V1_0::SensorFlagBits::WAKE_UP)) {
numWakeupEvents++;
}
}
return numWakeupEvents;
}
int32_t HalProxy::clearSubHalIndex(int32_t sensorHandle) {
return sensorHandle & (~kSensorHandleSubHalIndexMask);
}
bool HalProxy::subHalIndexIsClear(int32_t sensorHandle) {
return (sensorHandle & kSensorHandleSubHalIndexMask) == 0;
}
} // namespace implementation
} // namespace V2_1
} // namespace sensors
} // namespace hardware
} // namespace android

View File

@@ -1,84 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "HalProxyCallback.h"
#include <cinttypes>
namespace android {
namespace hardware {
namespace sensors {
namespace V2_0 {
namespace implementation {
static constexpr int32_t kBitsAfterSubHalIndex = 24;
/**
* Set the subhal index as first byte of sensor handle and return this modified version.
*
* @param sensorHandle The sensor handle to modify.
* @param subHalIndex The index in the hal proxy of the sub hal this sensor belongs to.
*
* @return The modified sensor handle.
*/
int32_t setSubHalIndex(int32_t sensorHandle, size_t subHalIndex) {
return sensorHandle | (static_cast<int32_t>(subHalIndex) << kBitsAfterSubHalIndex);
}
void HalProxyCallbackBase::postEvents(const std::vector<V2_1::Event>& events,
ScopedWakelock wakelock) {
if (events.empty() || !mCallback->areThreadsRunning()) return;
size_t numWakeupEvents;
std::vector<V2_1::Event> processedEvents = processEvents(events, &numWakeupEvents);
if (numWakeupEvents > 0) {
ALOG_ASSERT(wakelock.isLocked(),
"Wakeup events posted while wakelock unlocked for subhal"
" w/ index %" PRId32 ".",
mSubHalIndex);
} else {
ALOG_ASSERT(!wakelock.isLocked(),
"No Wakeup events posted but wakelock locked for subhal"
" w/ index %" PRId32 ".",
mSubHalIndex);
}
mCallback->postEventsToMessageQueue(processedEvents, numWakeupEvents, std::move(wakelock));
}
ScopedWakelock HalProxyCallbackBase::createScopedWakelock(bool lock) {
ScopedWakelock wakelock(mRefCounter, lock);
return wakelock;
}
std::vector<V2_1::Event> HalProxyCallbackBase::processEvents(const std::vector<V2_1::Event>& events,
size_t* numWakeupEvents) const {
*numWakeupEvents = 0;
std::vector<V2_1::Event> eventsOut;
for (V2_1::Event event : events) {
event.sensorHandle = setSubHalIndex(event.sensorHandle, mSubHalIndex);
eventsOut.push_back(event);
const V2_1::SensorInfo& sensor = mCallback->getSensorInfo(event.sensorHandle);
if ((sensor.flags & V1_0::SensorFlagBits::WAKE_UP) != 0) {
(*numWakeupEvents)++;
}
}
return eventsOut;
}
} // namespace implementation
} // namespace V2_0
} // namespace sensors
} // namespace hardware
} // namespace android

View File

@@ -1,11 +0,0 @@
<manifest version="1.0" type="device">
<hal format="hidl">
<name>android.hardware.sensors</name>
<transport>hwbinder</transport>
<version>2.0</version>
<interface>
<name>ISensors</name>
<instance>default</instance>
</interface>
</hal>
</manifest>

View File

@@ -1,7 +0,0 @@
service vendor.sensors-hal-2-0-multihal /vendor/bin/hw/android.hardware.sensors@2.0-service.multihal
class hal
user system
group system wakelock context_hub input
writepid /dev/cpuset/system-background/tasks
capabilities BLOCK_SUSPEND
rlimit rtprio 10 10

View File

@@ -1,39 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android/hardware/sensors/2.0/ISensors.h>
#include <hidl/HidlTransportSupport.h>
#include <log/log.h>
#include <utils/StrongPointer.h>
#include "HalProxy.h"
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using android::hardware::sensors::V2_0::ISensors;
using android::hardware::sensors::V2_1::implementation::HalProxyV2_0;
int main(int /* argc */, char** /* argv */) {
configureRpcThreadpool(1, true);
android::sp<ISensors> halProxy = new HalProxyV2_0();
if (halProxy->registerAsService() != ::android::OK) {
ALOGE("Failed to register Sensors HAL instance");
return -1;
}
joinRpcThreadpool();
return 1; // joinRpcThreadpool shouldn't exit
}

6
sepolicy/vendor/devicesettings_app.te vendored Normal file
View File

@@ -0,0 +1,6 @@
type devicesettings_app, domain;
app_domain(devicesettings_app)
# Allow charging bypass to overwrite
allow devicesettings_app sysfs_battery_writable:dir r_dir_perms;
allow devicesettings_app sysfs_battery_writable:file { open read write getattr ioctl };

View File

@@ -13,6 +13,9 @@ type proc_oppo_fp, fs_type, proc_type;
# Camera
type vendor_persist_camera_file, file_type;
# Bypass Charging
type sysfs_battery_writable, sysfs_type, fs_type;
# Devinfo
type proc_devinfo, fs_type, proc_type;

View File

@@ -16,6 +16,12 @@
# Camera
/mnt/vendor/persist/camera(/.*)? u:object_r:vendor_persist_camera_file:s0
# Cust
/dev/block/platform/soc/4744000.sdhci/by-name/cust u:object_r:vendor_custom_block_device:s0
# Charging Bypass
/sys/class/power_supply/battery/input_suspend u:object_r:sysfs_battery_writable:s0
# Oppo Partitions
/dev/block/platform/soc/1d84000.ufshc/by-name/oppodycnvbk u:object_r:vendor_modem_efs_partition_device:s0
/dev/block/platform/soc/1d84000.ufshc/by-name/oppostanvbk u:object_r:vendor_modem_efs_partition_device:s0

View File

@@ -1,4 +1,5 @@
allow hal_power_default proc_touchpanel:dir search;
allow hal_power_default proc_touchpanel:file rw_file_perms;
allow init sysfs_battery_supply:file rw_file_perms;
get_prop(hal_power_default, vendor_mpctl_prop);

View File

@@ -6,5 +6,9 @@ r_dir_file(system_app, proc_touchpanel);
r_dir_file(system_app, vendor_sysfs_usb_supply);
allow system_app vendor_sysfs_usb_supply:file rw_file_perms;
allow system_app vendor_sysfs_battery_supply:dir search;
allow system_app vendor_sysfs_battery_supply:file read;
allow system_app vendor_sysfs_battery_supply:file getattr;
# Allow Settings app to control input_suspend (bypass charging)
allow system_app vendor_sysfs_battery_supply:file rw_file_perms;

4
sepolicy/vendor/system_server.te vendored Normal file
View File

@@ -0,0 +1,4 @@
allow system_server proc_devinfo:dir search;
allow system_server proc_oppo_rf:dir search;
allow system_server proc_oppoversion:dir search;
allow system_server proc_touchpanel:dir search;

View File

@@ -6,3 +6,7 @@ get_prop(vendor_qti_init_shell, oppo_debug_prop)
allow vendor_qti_init_shell sysfs:file write;
allow vendor_qti_init_shell proc_page_cluster:file { open };
# To set powerhal init property
set_prop(vendor_qti_init_shell, vendor_power_prop)

View File

@@ -21,11 +21,13 @@ ro.camera.attr.detect.enable=1
ro.camera.notify_nfc=1
ro.camera.temperature.limit=470
persist.vendor.camera.light_ae.enable=1
persist.camera.eis.enable=1
persist.camera.is_mode=4
persist.vendor.camera.oissr.debug=1
vendor.debug.camera.eisv2enable=1
vendor.debug.camera.eisv3enable=1
# Allow privileged camera apps
persist.vendor.camera.privapp.list=com.android.camera,com.google.camera,org.lineageos.aperture
# Allow aux cameras
vendor.camera.aux.packagelist=com.android.camera,com.google.camera,org.lineageos.aperture
# CNE
persist.vendor.cne.feature=1

View File

@@ -97,6 +97,8 @@ vendor.qcom.bluetooth.soc=cherokee
# Camera
camera.disable_zsl_mode=1
persist.vendor.camera.eis.enable=1
persist.vendor.camera.is_type=2
# Crypto
ro.crypto.allow_encrypt_override=true
@@ -222,3 +224,7 @@ sys.fflag.override.settings_seamless_transfer=true
# default refresh rate threshold
debug.sf.frame_rate_multiple_threshold=0
# Fingerprint
ro.vendor.fingerprint.type=side
ro.vendor.fingerprint.supports_gestures=true