├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── Doc ├── PasteImage │ ├── 2023-07-11-13-32-45.png │ ├── 2023-07-11-15-14-15.png │ ├── 2023-07-11-15-42-04.png │ ├── 2023-07-11-15-42-19.png │ ├── 2023-07-11-15-43-02.png │ ├── 2023-08-02-20-37-49.png │ └── Screenshot_2023-08-19-20-19-45-946_com.xk.flash.jpg ├── activity_readme.txt ├── old.md ├── 原理介绍.md ├── 屏幕亮度记录.xlsx ├── 笔记.md └── 隐私政策.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── cjy.jks ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── cjyyxn │ │ └── screenfilter │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── cjyyxn │ │ │ └── screenfilter │ │ │ ├── AppAccessibilityService.java │ │ │ ├── AppConfig.java │ │ │ ├── BrightnessManager.java │ │ │ ├── FilterViewManager.java │ │ │ ├── GlobalStatus.java │ │ │ ├── MainActivity.java │ │ │ ├── quicksetting │ │ │ ├── QuickSettingFilter.java │ │ │ ├── QuickSettingIntelligentBrightness.java │ │ │ └── QuickSettingScreenShot.java │ │ │ ├── ui │ │ │ ├── BrightnessPointActivity.java │ │ │ ├── DebugActivity.java │ │ │ ├── MainUI.java │ │ │ ├── PreparatoryActivity.java │ │ │ └── ReadmeActivity.java │ │ │ └── utils │ │ │ ├── CombinationControl.java │ │ │ ├── TimerControl.java │ │ │ └── TriConsumer.java │ └── res │ │ ├── drawable │ │ ├── filter.png │ │ ├── intelligent_brightness.png │ │ └── screenshot.png │ │ ├── layout │ │ ├── activity_brightness_point.xml │ │ ├── activity_debug.xml │ │ ├── activity_main.xml │ │ ├── activity_preparatory.xml │ │ ├── activity_readme.xml │ │ ├── dialog_brightness_point.xml │ │ ├── dialog_privacy_policy.xml │ │ ├── jumplabel_control.xml │ │ ├── list_brightness_point.xml │ │ ├── seekbar_control.xml │ │ └── switch_control.xml │ │ ├── mipmap │ │ └── ic_launcher.png │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── app_accessibility.xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── line_point_formatter_with_labels.xml │ └── test │ └── java │ └── com │ └── cjyyxn │ └── screenfilter │ └── ExampleUnitTest.java ├── assets ├── Screenshot_1.jpg ├── Screenshot_2.jpg ├── Screenshot_3.jpg ├── Screenshot_4.jpg ├── Screenshot_5.jpg └── ic_launcher.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Doc/PasteImage/2023-07-11-13-32-45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/Doc/PasteImage/2023-07-11-13-32-45.png -------------------------------------------------------------------------------- /Doc/PasteImage/2023-07-11-15-14-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/Doc/PasteImage/2023-07-11-15-14-15.png -------------------------------------------------------------------------------- /Doc/PasteImage/2023-07-11-15-42-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/Doc/PasteImage/2023-07-11-15-42-04.png -------------------------------------------------------------------------------- /Doc/PasteImage/2023-07-11-15-42-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/Doc/PasteImage/2023-07-11-15-42-19.png -------------------------------------------------------------------------------- /Doc/PasteImage/2023-07-11-15-43-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/Doc/PasteImage/2023-07-11-15-43-02.png -------------------------------------------------------------------------------- /Doc/PasteImage/2023-08-02-20-37-49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/Doc/PasteImage/2023-08-02-20-37-49.png -------------------------------------------------------------------------------- /Doc/PasteImage/Screenshot_2023-08-19-20-19-45-946_com.xk.flash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/Doc/PasteImage/Screenshot_2023-08-19-20-19-45-946_com.xk.flash.jpg -------------------------------------------------------------------------------- /Doc/activity_readme.txt: -------------------------------------------------------------------------------- 1 | 应用简介 2 | \n 3 | 对于 OLED 屏幕的手机,一般情况下,屏幕亮度越低,频闪越强。本应用控制屏幕具有较高的亮度,并通过给屏幕添加一层不透明度可调的黑色滤镜来调节实际亮度,从而实现**低亮度下也有低频闪**的效果。 4 | 5 | \n\n 6 | 7 | 项目源码 8 | \n 9 | https://github.com/cjyyx/ScreenFilter 10 | \n 11 | 应用原理 12 | \n 13 | https://zhuanlan.zhihu.com/p/642886728 14 | 15 | \n\n 16 | 17 | 下载地址 18 | \n 19 | 下载链接 1:github release 20 | \n 21 | https://github.com/cjyyx/ScreenFilter/releases 22 | \n 23 | 下载链接 2:蓝奏云 24 | \n 25 | https://wwis.lanzouq.com/b04whksif 26 | \n 27 | 密码:1234 28 | 29 | \n\n 30 | 31 | 注意: 32 | \n- 支持直接拖动系统状态栏亮度条来控制亮度 33 | \n- 当环境光照较高时,应用会自动关闭屏幕滤镜并打开系统自动亮度,从而使屏幕能够达到最大激发亮度 34 | \n- 开启滤镜时不要开启系统纸质护眼,否则会造成花屏 35 | 36 | \n\n 37 | 38 | 解释: 39 | \n- 屏幕滤镜开关:打开关闭屏幕滤镜;注意开启滤镜时不要开启系统纸质护眼,否则会造成花屏;支持状态栏快捷设置磁贴 40 | \n- 智能亮度开关:打开关闭智能亮度;控制屏幕实际亮度处于 [ 通过环境光照和亮度-光照曲线计算得的屏幕亮度 - 亮度调高容差, 通过环境光照和亮度-光照曲线计算得的屏幕亮度 + 亮度调低容差 ] 这个区间;支持状态栏快捷设置磁贴 41 | \n- 正常截屏:状态栏快捷设置磁贴,关闭屏幕滤镜截图,之后恢复屏幕滤镜 42 | \n- 在多任务界面隐藏:字面意思 43 | \n- 屏幕亮度设置:与系统状态栏亮度条同步 44 | \n- 亮光模式阈值:当环境光照超过阈值时,应用会自动关闭屏幕滤镜并打开系统自动亮度,从而使屏幕能够达到最大激发亮度 45 | \n- 暗光模式阈值:当环境光照低于阈值且屏幕亮度设置条(系统状态栏亮度条)被拖到最低时,设置屏幕亮度为最低值,即系统屏幕亮度为最低硬件亮度,滤镜不透明度为最高滤镜不透明度,屏幕实际亮度 = 最低硬件亮度 * ( 1 - 最高滤镜不透明度 )^2 46 | \n- 最低硬件亮度:最低硬件亮度应设置为手机屏幕关闭类 DC 调光的阈值 47 | \n- 最高滤镜不透明度:可以调为暗光模式下最舒适的屏幕亮度 48 | \n- 亮度调高容差:与智能亮度调节有关 49 | \n- 亮度调低容差:与智能亮度调节有关 50 | \n- 亮度-光照曲线设置界面:可以通过增减修改光照-亮度对应点来调整亮度-光照曲线 -------------------------------------------------------------------------------- /Doc/old.md: -------------------------------------------------------------------------------- 1 | ## 依赖 2 | 3 | https://github.com/halfhp/androidplot 4 | 5 | ## 软件使用逻辑 6 | 7 | ### 主界面 8 | 9 | 打开软件,打开准备界面(提示提供相应权限)(之后操作也要权限检查,不通过则放弃操作,且打开准备页面) 10 | 11 | 软件主界面,有 12 | 13 | - 滤镜开关(支持状态栏快捷设置) 14 | - 智能亮度开关(支持状态栏快捷设置)(关闭系统自动亮度,自己实现智能亮度) 15 | - 最低硬件亮度拖动条(最低硬件亮度一般为手机屏幕关闭类 DC 调光的阈值) 16 | - 最高滤镜不透明度拖动条,可以调整夜间全黑环境下最舒适的屏幕亮度 17 | - 高光照阈值拖动条(光照达到阈值,打开系统自动亮度,使屏幕达到最大激发亮度) 18 | - 亮度-光照曲线设置 19 | 20 | ### 光照控制亮度逻辑 21 | 22 | 通过亮度-光照曲线来实现。点击主界面的亮度-光照曲线设置按钮,打开亮度-光照曲线设置界面。可以通过增减修改光照-亮度对应点来调整亮度-光照曲线。 23 | 24 | 智能亮度逻辑通过有限状态机实现。有光照平稳、光照突增、光照突减、超高光照、用户调整这几种状态。 25 | 26 | 光照平稳、光照突增、光照突减:根据亮度-光照曲线设置屏幕亮度 27 | 28 | 高光照:打开系统自动亮度,使屏幕达到最大激发亮度 29 | 30 | 用户调整:在用户锁屏之前,或一定时间内,以用户调整的亮度为准 31 | 32 | 33 | 34 | ### 状态栏控制 35 | 36 | 状态栏快速设置服务,有屏幕滤镜开关、智能亮度开关、正常截图功能 37 | 38 | 软件正常运行时,应关闭系统自动亮度。 39 | 40 | 用户调整状态栏亮度调,会改变系统亮度,可以据此设置屏幕亮度。 41 | 42 | 43 | ## 项目架构、伪代码 44 | 45 | ### MainActivity, MainUI 46 | 47 | 主界面 48 | 49 | #### 启动时 50 | 51 | 检查权限,打开准备界面 52 | 53 | 读取存储的应用设置或默认设置,保存到 `GlobalStatus` (应用存储变量,`BrightnessManager`) 54 | 55 | ### PreparatoryActivity 56 | 57 | 准备界面,使用户提供相应权限 58 | 59 | ### BrightnessPointActivity 60 | 61 | 用来设置亮度-光照曲线 62 | 63 | 通过 `GlobalStatus` 修改光照-亮度对应点列表,注意零光照点和高光照阈值点必须存在 64 | 65 | 66 | ### FilterViewManager 67 | 68 | 管理屏幕滤镜 69 | 70 | ### FilterAccessibilityService 71 | 72 | 无障碍服务,用户启用无障碍功能时被创建 73 | 74 | #### 启动时 75 | 76 | 创建 `FilterViewManager`,加入到 `GlobalStatus` 77 | 78 | #### 运行时 79 | 80 | 监视光照,更新至 `GlobalStatus`,光照改变时调用 `brightnessManager.onLightChanged(float light)` 81 | 82 | 监视系统亮度,更新至 `GlobalStatus`,用户改变系统亮度时调用 `brightnessManager.onSystemBrightnessChangedByUser(float brightness)` 83 | 84 | ### GlobalStatus 85 | 86 | 全局变量、方法 87 | 88 | #### 应用存储变量 89 | 90 | - minHardwareBrightness: 最低硬件亮度,当高于此亮度时,屏幕应为类 DC 调光 91 | - maxFilterOpacity: 最高滤镜不透明度 92 | - highLightThreshold: 高光照阈值 93 | 94 | 95 | #### float light 96 | 97 | 当前传感器获得的光照强度,单位 lux 98 | 99 | #### float userBrightness 100 | 101 | 当前用户设置的屏幕亮度,范围 [0,1] 102 | 103 | #### void setFilterViewManager(FilterViewManager f) 104 | 105 | #### void setBrightnessManager(BrightnessManager bm) 106 | 107 | #### boolean isAccessibility() 108 | 109 | 判断是否具有足够的权限 110 | 111 | #### void openPreparatoryActivity() 112 | 113 | 打开准备界面 114 | 115 | #### void openFilter() 116 | 117 | 检查权限,打开滤镜 118 | 119 | #### void closeFilter() 120 | 121 | 关闭滤镜 122 | 123 | #### void setAlpha(float alpha) 124 | 125 | 调用 `FilterViewManager` 设置滤镜不透明度 126 | 127 | `alpha` 取值 [0,1], 0 表示完全透明,1 表示完全不透明 128 | 129 | 134 | 135 | #### void openIntelligentBrightness() 136 | 137 | 检查权限,打开智能亮度 138 | 139 | #### void closeIntelligentBrightness() 140 | 141 | 关闭智能亮度 142 | 143 | #### list getBrightnessPointList() 144 | 145 | 返回光照-亮度对应点列表 146 | 147 | #### void addBrightnessPoint(float light, float brightness) 148 | 149 | 添加光照-亮度对应点,同时更新应用存储 150 | 151 | #### void delBrightnessPoint(int id) 152 | 153 | 删除光照-亮度对应点,同时更新应用存储 154 | 155 | #### void setBrightnessPoint(int id, float light, float brightness) 156 | 157 | 设置光照-亮度对应点,同时更新应用存储 158 | 159 | #### void onLightChanged(float light) 160 | 161 | 当传感器获取的光照强度改变时被调用,`light` 单位为 lux 162 | 163 | 当智能亮度开时,根据光照,计算得相应亮度,计算得相应系统亮度并设置,计算得相应滤镜不透明度并设置 164 | 165 | #### void onSystemBrightnessChangedByUser(float brightness) 166 | 167 | 用户改变系统亮度时被调用 168 | 169 | brightness 范围 [0,1] 170 | 171 | ### BrightnessManager 172 | 173 | 实现光照控制亮度逻辑 174 | 175 | 光照-亮度对应点 (光照强度{[0,+inf] lux}, 屏幕亮度{[0,1]}) 176 | 177 | ### QuickSettingFilter 178 | 179 | 状态栏快速设置服务,开关屏幕滤镜 180 | 181 | ### QuickSettingScreenShot 182 | 183 | 状态栏快速设置服务,关闭屏幕滤镜,调用屏幕截图功能,再打开屏幕截图 184 | 185 | ### QuickSettingIntelligentBrightness 186 | 187 | 状态栏快速设置服务,开关智能亮度 -------------------------------------------------------------------------------- /Doc/原理介绍.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # 影响护眼的因素 9 | 10 | ## 蓝光 11 | 12 | 目前手机大多已经实现硬件低蓝光,而且蓝光也可以通过护眼模式轻易克服。 13 | 14 | ## 偏振光 15 | 16 | 偏振光指振动方向与传播方向不对称的光,主要分为圆偏振光与线偏振光两种。 17 | 18 | 线偏振光测试方法为:透过偏振片看屏幕,旋转偏振片,若存在某个角度屏幕发出的光线无法透过偏振片,则是线偏振光。一些墨镜镜片、相机的cpl镜都可以作为偏振片使用。 19 | 20 | 大部分LCD屏幕是线偏振光[^6],少部分OLED屏幕(如红米note12turbo)也是线偏振光。 21 | 22 | 2009年闫晓林等人让随机分组的测试者观看圆偏振光电视和普通液晶电视,进行分级视力和眨眼频率测试。通过分级视力变化和眨眼频率反映视疲劳程度。分析得出,当观看两种偏振光电视一段时间后,不论是儿童还是成人都会产生暂时视力下降。**圆偏振光与线偏振光相比,引起的视觉疲劳程度会小一些**。研究者推测是由于圆偏振光本身与线偏振光相比,其振动面不只限于某一固定方向,而是围绕光的前进方向转动,旋转电矢量端点描出均匀圆轨迹,这与自然光的振动面在各个方向上均匀分布是比较接近的,因而可能产生的视疲劳较轻。[^5] 23 | 24 | 在屏幕贴膜之后,线偏振光的成分会减少,因此可以作为护眼的手段。 25 | 26 | ## 眩光 27 | 28 | 眩光是一种影响视觉的机制。它是指视野中亮度分布不均匀、亮度范围变化不适宜、或者时间和空间上存在极端对比,造成人眼在观看时的不舒适之感或观察细部物体能力降低的现象。按照眩光产生的标准,可以分为直接眩光和反射眩光。 29 | 30 | 贴AR抗反射膜,可以减少反射眩光。 31 | 32 | # 屏幕的频闪 33 | 34 | ## 频闪的度量 35 | 36 | 目前手机屏幕频闪的度量主要有两种方式,一种是用低快门时间的相机拍摄手机屏幕,观察黑色条纹;另一种是高时间分辨率的照度探头,测出屏幕上指定区域的亮度随时间变化曲线,再通过一定的公式计算出频闪效应可见性度量值(SVM, Stroboscopic effect visibility measure)。 37 | 38 | ### 相机拍摄 39 | 40 | 相机拍摄的方式相当简单,只要有一部手机,就可以观察频闪程度。具体方法为,将手机相机调到专业模式,将快门时间调到 1/4000 秒以下,对准被测手机屏幕,然后可以看到黑色条纹,如图所示。 41 | 42 | ![](PasteImage/2023-08-02-20-37-49.png) 43 | 44 | 一般来说,黑色条纹**越宽、颜色越深、越稀疏**,频闪程度越强。 45 | 46 | ### SVM 计算方法[^3] 47 | 48 | 传感器测得的照度随时间变化产生波形。将波形归一化,使时间平均值等于 1 ,得到相对照度波形,记为 $y(t)$,并进行三角傅里叶级数展开 49 | 50 | $$ 51 | y(t) = \dfrac{a_0}{2} + \sum\limits_{m=1}^{\infty} \left[ a_m\cos\left(\dfrac{2\pi m t}{T}\right) + b_m\sin\left(\dfrac{2\pi m t}{T}\right) \right] 52 | $$ 53 | 54 | 相对照度波形的第 $m$ 个傅里叶分量的相对幅度记为 $C_{m} = \sqrt{a_m^2 + b_m^2}$,频率记为 $f_{m} = \dfrac{m}{T}$。 55 | 56 | 考虑频闪效应对比度阈值函数(stroboscopic effect contrast threshold function) 57 | 58 | ![](PasteImage/2023-07-11-15-14-15.png) 59 | 60 | 记 $T_{m}$ 为频率 $f_{m}$ 对应的频闪效应对比度阈值函数值。 61 | 62 | 63 | 则 SVM 计算公式如下 64 | 65 | $$ 66 | \mathrm{SVM}=\left[~\sum\limits_{m=1}^{\infty}\left(\dfrac{C_{m}}{T_{m}}\right)^{3.7}~\right]^{1/3.7} 67 | $$ 68 | 69 | SVM 值越高,频闪程度越高。而且 SVM 值是可以进行精确计算的,因此可以把 SVM 作为频闪分析的理论依据。 70 | 71 | 观察 SVM 的计算过程,可以发现其取值**与屏幕亮度绝对值无关,只与亮度随时间变化曲线的形状有关**。 72 | 73 | ## 亮度越高,频闪越低 74 | 75 | 这个结论非常容易验证。最直接的,B站up主低调的山用相机拍摄过大量OLED屏幕,都有在高亮度下低频闪,在低亮度下高频闪的现象[^1]。 76 | 77 | 更进一步的,up主Navis-慢点评测展示了OLED手机屏幕 SVM 随屏幕亮度变化曲线[^2]。 78 | 79 | ![](PasteImage/2023-07-11-13-32-45.png) 80 | 81 | up主先看评测制作APP先看频闪,同样展示了OLED手机屏幕 SVM 随屏幕亮度变化曲线[^4]。 82 | 83 | ![](PasteImage/Screenshot_2023-08-19-20-19-45-946_com.xk.flash.jpg) 84 | 85 | 因此可以得出结论,一般情况下,OLED 屏幕亮度越高,频闪越低。结合分析 SVM 计算过程得到的结论,有降低屏幕频闪的方法:**维持屏幕在高亮度,通过增加一个不透明度可调节的黑色滤镜来控制屏幕实际亮度,从而实现在低亮度下也有低频闪**,这就是通过屏幕滤镜降低手机频闪的原理。 86 | 87 | ## 屏幕滤镜的局限 88 | 89 | 之前提到,屏幕滤镜可以降低屏幕亮度,同时维持亮度随时间变化曲线的形状不变。而 SVM 只取决于亮度随时间变化曲线的形状。因此,屏幕滤镜可以让 SVM 一直处于最小值即频闪最低的同时降低屏幕亮度。 90 | 91 | 但是,真实情况并非如此。屏幕频闪由pwm调光和像素刷新共同决定。高亮度下pwm调光占主导,超低亮度下像素刷新占主导地位。这是因为超低亮度的时候pwm调光的波动不如像素刷新的波动大,120Hz 刷新率,每一次刷新就需要关闭再点亮一次像素,这个重新点亮就意味着频闪[^7]。 92 | 93 | 所以,**即使使用了屏幕滤镜,在超低亮度下也会存在 120Hz 的频闪!** 94 | 95 | ## 屏幕滤镜在安卓系统的实现 96 | 97 | 幸运的是,安卓系统给出了足够的 api,使我们能够实现屏幕滤镜。 98 | 99 | 首先,app 需要打开无障碍服务,获取显示在整个屏幕上的权限。 100 | 101 | 参考: 102 | 103 | 开启无障碍服务后,利用无障碍服务的上下文获取整个屏幕的窗口管理器,往窗口管理器添加纯黑色、透明度可调的视图对象,和相应的参数对象,就实现了屏幕滤镜。 104 | 105 | 将无障碍服务上下文传入下面代码的 `FilterViewManager` 对象,即可在屏幕上显示一个透明度可调的黑色滤镜。 106 | 107 | ```java 108 | import android.content.Context; 109 | import android.graphics.Color; 110 | import android.graphics.PixelFormat; 111 | import android.os.Handler; 112 | import android.os.Looper; 113 | import android.view.View; 114 | import android.view.WindowManager; 115 | 116 | public class FilterViewManager { 117 | 118 | private final Context context; 119 | private final WindowManager windowManager; 120 | private final WindowManager.LayoutParams layoutParams; 121 | private final FilterView filterView; 122 | /** 123 | * 滤镜处于开启状态,为 true 124 | */ 125 | public boolean isOpen; 126 | private float alpha = 0f; 127 | private float hardwareBrightness = 0f; 128 | 129 | public FilterViewManager(Context c) { 130 | // 这里假设传入的 Context 有无障碍权限,后面的代码不对无障碍权限进行检验 131 | 132 | isOpen = false; 133 | context = c; 134 | windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 135 | layoutParams = new WindowManager.LayoutParams(); 136 | filterView = new FilterView(context); 137 | 138 | layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; 139 | // width 和 height 尽可能大,从而覆盖屏幕 140 | layoutParams.width = 4000; 141 | layoutParams.height = 4000; 142 | layoutParams.format = PixelFormat.TRANSLUCENT; 143 | layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 144 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | 145 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 146 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | 147 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 148 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 149 | } 150 | 151 | public void open() { 152 | new Handler(Looper.getMainLooper()).post(() -> { 153 | // 在UI线程中更新UI组件 154 | if (!isOpen) { 155 | windowManager.addView(filterView, layoutParams); 156 | isOpen = true; 157 | } 158 | }); 159 | } 160 | 161 | public void close() { 162 | new Handler(Looper.getMainLooper()).post(() -> { 163 | // 在UI线程中更新UI组件 164 | if (isOpen) { 165 | windowManager.removeView(filterView); 166 | isOpen = false; 167 | } 168 | }); 169 | } 170 | 171 | public float getAlpha() { 172 | if (isOpen) { 173 | return alpha; 174 | } else { 175 | return -1f; 176 | } 177 | } 178 | 179 | public void setAlpha(float alpha) { 180 | new Handler(Looper.getMainLooper()).post(() -> { 181 | if (isOpen) { 182 | float a = Math.min(1f, Math.max(0f, alpha)); 183 | // 在UI线程中更新UI组件 184 | filterView.setAlpha(a); 185 | this.alpha = a; 186 | } 187 | }); 188 | } 189 | 190 | public float getHardwareBrightness() { 191 | if (isOpen) { 192 | return hardwareBrightness; 193 | } else { 194 | return -1f; 195 | } 196 | } 197 | 198 | public void setHardwareBrightness(float brightness) { 199 | new Handler(Looper.getMainLooper()).post(() -> { 200 | if (isOpen) { 201 | float b = Math.min(1f, Math.max(0f, brightness)); 202 | // 在UI线程中更新UI组件 203 | // layoutParams.screenBrightness 会覆盖系统亮度设置 204 | layoutParams.screenBrightness = b; 205 | windowManager.updateViewLayout(filterView, layoutParams); 206 | hardwareBrightness = b; 207 | } 208 | }); 209 | } 210 | 211 | private static class FilterView extends View { 212 | 213 | public FilterView(Context context) { 214 | super(context); 215 | setBackgroundColor(Color.BLACK); 216 | setAlpha(0f); 217 | } 218 | 219 | @Override 220 | public void setAlpha(float alpha) { 221 | super.setAlpha(alpha); 222 | invalidate(); 223 | } 224 | } 225 | } 226 | ``` 227 | 228 | 229 | ## 开源 APP:滤镜护眼防频闪 230 | 231 | github 项目: 232 | 233 | 对于 OLED 屏幕的手机,一般情况下,屏幕亮度越低,频闪越强。本应用控制屏幕具有较高的亮度,并通过给屏幕添加一层不透明度可调的黑色滤镜来调节实际亮度,从而实现**低亮度下也有低频闪**的效果。 234 | 235 | 注意: 236 | 237 | 1. 支持直接拖动系统状态栏亮度条来控制亮度 238 | 2. 当环境光照较高时,应用会自动关闭屏幕滤镜并打开系统自动亮度,从而使屏幕能够达到最大激发亮度 239 | 3. 最低支持版本安卓10 240 | 4. 本应用在开发时没有考虑兼容性,目前只能保证在我的手机上正常运行。我的手机系统是 MIUI14 241 | 5. 开启滤镜时不要开启系统纸质护眼,否则会造成花屏 242 | 243 | 下载链接 1:github release 244 | 245 | 246 | 下载链接 2:123云盘 247 | 248 | 249 | 250 | 251 | 252 | [^1]: 低调的山, 253 | [^2]: Navis-慢点评测, 254 | [^3]: 维基百科, 255 | [^4]: 先看频闪, 256 | [^5]: 张平奇,王丹,吕振华,等.健康显示的影响因素综述[J].液晶与显示,2020,35(09):981-990. 257 | [^6]: 圆偏振光和线偏振光测试, 258 | [^7]: 像素刷新稀释, 259 | -------------------------------------------------------------------------------- /Doc/屏幕亮度记录.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/Doc/屏幕亮度记录.xlsx -------------------------------------------------------------------------------- /Doc/笔记.md: -------------------------------------------------------------------------------- 1 | ## 光线传感器 2 | 3 | https://blog.csdn.net/hello_1995/article/details/119890052 4 | 5 | 监听器 6 | 7 | ```java 8 | private class MySensorEventListener implements SensorEventListener { 9 | 10 | @Override 11 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 12 | Log.d("myLog", "onAccuracyChanged:" + sensor.getType() + "->" + accuracy); 13 | } 14 | 15 | @Override 16 | public void onSensorChanged(SensorEvent sensorEvent) { 17 | if (sensorEvent.sensor.getType() == Sensor.TYPE_LIGHT){ 18 | String msg = "lux: "+ sensorEvent.values[0]; 19 | Log.d("myLog", msg); 20 | } 21 | } 22 | } 23 | ``` 24 | 25 | 进行监听 26 | 27 | ```java 28 | this.mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); 29 | this.mMySensorEventListener = new MySensorEventListener(); 30 | 31 | if (mSensorManager != null){ 32 | Sensor lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); 33 | if (lightSensor != null) { 34 | mSensorManager.registerListener(mMySensorEventListener, lightSensor, SensorManager.SENSOR_DELAY_NORMAL); 35 | } 36 | } 37 | ``` 38 | 39 | 注销传感器的监听器 40 | 41 | ```java 42 | @Override 43 | protected void onPause() { 44 | super.onPause(); 45 | if (mSensorManager != null) { 46 | mSensorManager.unregisterListener(mMySensorEventListener); 47 | } 48 | } 49 | ``` 50 | 51 | ## 获取系统亮度 52 | 53 | ```java 54 | /** 55 | * 获取系统亮度, 屏幕亮度值范围(0-255) 56 | */ 57 | private int getScreenBrightness(Context context) { 58 | ContentResolver contentResolver = context.getContentResolver(); 59 | int defVal = 125; 60 | return Settings.System.getInt(contentResolver, 61 | Settings.System.SCREEN_BRIGHTNESS, defVal); 62 | } 63 | ``` 64 | 65 | 调用时 66 | 67 | ```java 68 | getScreenBrightness(getApplicationContext()) 69 | ``` 70 | 71 | ## 系统亮度设置 72 | 73 | https://blog.csdn.net/MLQ8087/article/details/103704891 74 | 75 | ```java 76 | /** 77 | * 修改 Setting 中屏幕亮度值 78 | **/ 79 | private void ModifySettingsScreenBrightness(Context context, int birghtessValue) { 80 | ContentResolver contentResolver = context.getContentResolver(); 81 | Settings.System.putInt(contentResolver, 82 | Settings.System.SCREEN_BRIGHTNESS, birghtessValue); 83 | } 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /Doc/隐私政策.md: -------------------------------------------------------------------------------- 1 | 滤镜护眼防频闪 隐私政策 2 | \n欢迎您访问我们的产品。 滤镜护眼防频闪 (包括App等产品提供的服务,以下简称“产品和服务”)是由 cjyyxn (以下简称“我们”)开发并运营的。 确保用户的数据安全和隐私保护是我们的首要任务, 本隐私政策载明了您访问和使用我们的产品和服务时所收集的数据及其处理方式。 3 | \n请您在继续使用我们的产品前务必认真仔细阅读并确认充分理解本隐私政策全部规则和要点, 一旦您选择使用,即视为您同意本隐私政策的全部内容,同意我们按其收集和使用您的相关信息。 如您在在阅读过程中,对本政策有任何疑问,可联系我们的客服咨询, 请通过 cjyyxn@qq.com 或产品中的反馈方式与我们取得联系。 如您不同意相关协议或其中的任何条款的,您应停止使用我们的产品和服务。 4 | \n本隐私政策帮助您了解以下内容: 5 | \n一、我们如何收集和使用您的个人信息; 6 | \n二、我们如何存储和保护您的个人信息; 7 | \n三、我们如何共享、转让、公开披露您的个人信息; 8 | \n一、我们如何收集和使用您的个人信息 9 | \n个人信息是指以电子或者其他方式记录的能够单独或者与其他信息, 结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。 由于我们的产品和服务并不需要此类信息,因此很高兴的告知您, 我们不会收集关于您的任何个人信息。 10 | \n二、我们如何存储和保护您的个人信息 11 | \n作为一般规则,我们仅在实现信息收集目的所需的时间内保留您的个人信息。 我们会在对于管理与您之间的关系严格必要的时间内保留您的个人信息 (例如,当您开立帐户,从我们的产品获取服务时)。 出于遵守法律义务或为证明某项权利或合同满足适用的诉讼时效要求的目的, 我们可能需要在上述期限到期后保留您存档的个人信息,并且无法按您的要求删除。 当您的个人信息对于我们的法定义务或法定时效对应的目的或档案不再必要时, 我们确保将其完全删除或匿名化。 12 | \n我们使用符合业界标准的安全防护措施保护您提供的个人信息,并加密其中的关键数据, 防止其遭到未经授权访问、公开披露、使用、修改、损坏或丢失。我们会采取一切合理可行的措施,保护您的个人信息。 我们会使用加密技术确保数据的保密性;我们会使用受信赖的保护机制防止数据遭到恶意攻击。 13 | \n三、我们如何共享、转让、公开披露您的个人信息 14 | \n在管理我们的日常业务活动所需要时,为追求合法利益以更好地服务客户, 我们将合规且恰当的使用您的个人信息。出于对业务和各个方面的综合考虑, 我们仅自身使用这些数据,不与任何第三方分享。 15 | \n我们可能会根据法律法规规定,或按政府主管部门的强制性要求,对外共享您的个人信息。 在符合法律法规的前提下,当我们收到上述披露信息的请求时,我们会要求必须出具与之相应的法律文件,如传票或调查函。 我们坚信,对于要求我们提供的信息,应该在法律允许的范围内尽可能保持透明。 16 | \n在以下情形中,共享、转让、公开披露您的个人信息无需事先征得您的授权同意: 17 | \n1、与国家安全、国防安全直接相关的; 18 | \n2、与犯罪侦查、起诉、审判和判决执行等直接相关的; 19 | \n3、出于维护您或其他个人的生命、财产等重大合法权益但又很难得到本人同意的; 20 | \n4、您自行向社会公众公开的个人信息; 21 | \n5、从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。 22 | \n6、根据个人信息主体要求签订和履行合同所必需的; 23 | \n7、用于维护所提供的产品或服务的安全稳定运行所必需的,例如发现、处置产品或服务的故障; 24 | \n8、法律法规规定的其他情形。 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 cjyyy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | P.S. 在 Google Play 上新发布的[屏幕调光器 — 减少闪烁](https://play.google.com/store/apps/details?id=dev.rewhex.screendimmer&hl=zh)也实现了相同功能! 2 | 3 | # 滤镜护眼防频闪 4 | 5 | ## 应用简介 6 | 7 | 对于 OLED 屏幕的手机,一般情况下,屏幕亮度越低,频闪越强。本应用控制屏幕具有较高的亮度,并通过给屏幕添加一层不透明度可调的黑色滤镜来调节实际亮度,从而实现**低亮度下也有低频闪**的效果。 8 | 9 | 注意: 10 | 11 | 1. 支持直接拖动系统状态栏亮度条来控制亮度 12 | 2. 当环境光照较高时,应用会自动关闭屏幕滤镜并打开系统自动亮度,从而使屏幕能够达到最大激发亮度 13 | 3. 最低支持版本安卓10 14 | 4. 本应用在开发时没有考虑兼容性,目前只能保证在我的手机上正常运行。我的手机系统是 MIUI14 15 | 5. 开启滤镜时不要开启系统纸质护眼,否则会造成花屏 16 | 17 | 本应用参考了开源项目 18 | 19 | ## 下载地址 20 | 21 | 下载链接 1:github release 22 | 23 | 24 | 下载链接 2:123云盘 25 | 26 | 27 | ## 应用原理 28 | 29 | 详见 30 | 31 | ## 应用截图 32 | 33 | ![](assets/Screenshot_1.jpg) 34 | ![](assets/Screenshot_2.jpg) 35 | ![](assets/Screenshot_3.jpg) 36 | ![](assets/Screenshot_4.jpg) 37 | 38 | ## 应用使用说明 39 | 40 | - 屏幕滤镜开关:打开关闭屏幕滤镜;注意开启滤镜时不要开启系统纸质护眼,否则会造成花屏;支持状态栏快捷设置磁贴 41 | - 智能亮度开关:打开关闭智能亮度;控制屏幕实际亮度处于 [ 通过环境光照和亮度-光照曲线计算得的屏幕亮度 - 亮度调高容差, 通过环境光照和亮度-光照曲线计算得的屏幕亮度 + 亮度调低容差 ] 这个区间;支持状态栏快捷设置磁贴 42 | - 正常截屏:状态栏快捷设置磁贴,关闭屏幕滤镜截图,之后恢复屏幕滤镜 43 | - 在多任务界面隐藏:字面意思 44 | - 屏幕亮度设置:与系统状态栏亮度条同步 45 | - 亮光模式阈值:当环境光照超过阈值时,应用会自动关闭屏幕滤镜并打开系统自动亮度,从而使屏幕能够达到最大激发亮度 46 | - 暗光模式阈值:当环境光照低于阈值且屏幕亮度设置条(系统状态栏亮度条)被拖到最低时,设置屏幕亮度为最低值,即系统屏幕亮度为最低硬件亮度,滤镜不透明度为最高滤镜不透明度,屏幕实际亮度 = 最低硬件亮度 * ( 1 - 最高滤镜不透明度 )^2 47 | - 最低硬件亮度:最低硬件亮度应设置为手机屏幕关闭类 DC 调光的阈值 48 | - 最高滤镜不透明度:可以调为暗光模式下最舒适的屏幕亮度 49 | - 亮度调高容差:与智能亮度调节有关 50 | - 亮度调低容差:与智能亮度调节有关 51 | - 亮度-光照曲线设置界面:可以通过增减修改光照-亮度对应点来调整亮度-光照曲线 52 | 53 | ## 项目依赖 54 | 55 | 使用了 androidplot 库 56 | 57 | ## 项目开发 58 | 59 | 可以直接用 Android Studio 打开项目。开发时使用的是 2022.2.1 版。 60 | 61 | ## 项目各模块简介 62 | 63 | ### GlobalStatus 64 | 65 | 使项目各模块解耦合。当一个模块要调用另一个模块的方法时,必须通过 GlobalStatus 66 | 67 | ### AppConfig 68 | 69 | 管理应用的配置 70 | 71 | ### Utils 72 | 73 | 通用的工具 74 | 75 | ### UI 76 | 77 | 与应用 UI 有关 78 | 79 | ### FilterViewManager 80 | 81 | 应用的核心模块,负责管理屏幕滤镜 82 | 83 | ### AppAccessibilityService 84 | 85 | 无障碍服务,用户启用无障碍功能时被创建 86 | 87 | ### BrightnessManager 88 | 89 | 实现光照控制亮度逻辑 90 | 91 | 光照-亮度对应点 (光照强度{[0,+inf] lux}, 屏幕亮度{[0,1]}) 92 | 93 | ### QuickSetting 94 | 95 | 与状态栏磁贴服务有关的模块位于 quicksetting 文件夹下 96 | 97 | ## Star History 98 | 99 | [![Star History Chart](https://api.star-history.com/svg?repos=cjyyx/ScreenFilter&type=Date)](https://star-history.com/#cjyyx/ScreenFilter&Date) 100 | 101 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.cjyyxn.screenfilter' 7 | compileSdk 33 8 | 9 | defaultConfig { 10 | applicationId "com.cjyyxn.screenfilter" 11 | minSdk 23 12 | targetSdk 33 13 | versionCode 7 14 | versionName "1.6" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_1_8 21 | targetCompatibility JavaVersion.VERSION_1_8 22 | } 23 | 24 | signingConfigs { 25 | release { 26 | keyAlias 'key0' 27 | keyPassword 'cjykey' 28 | storeFile file('cjy.jks') 29 | storePassword 'cjyyxn' 30 | } 31 | } 32 | buildTypes { 33 | release { 34 | minifyEnabled false 35 | signingConfig signingConfigs.release 36 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 37 | } 38 | debug { 39 | minifyEnabled false 40 | signingConfig signingConfigs.release 41 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 42 | } 43 | } 44 | } 45 | 46 | dependencies { 47 | 48 | implementation 'androidx.appcompat:appcompat:1.6.1' 49 | implementation 'com.google.android.material:material:1.5.0' 50 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 51 | testImplementation 'junit:junit:4.13.2' 52 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 53 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 54 | implementation "com.androidplot:androidplot-core:1.5.10" 55 | implementation 'com.google.code.gson:gson:2.10.1' 56 | } -------------------------------------------------------------------------------- /app/cjy.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/app/cjy.jks -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class com.androidplot.** { *; } -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.cjyyxn.screenfilter", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 7, 15 | "versionName": "1.6", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/cjyyxn/screenfilter/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.cjyyxn.screenfilter", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 19 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 58 | 63 | 68 | 69 | 78 | 79 | 80 | 81 | 82 | 91 | 92 | 93 | 94 | 95 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/AppAccessibilityService.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.hardware.Sensor; 5 | import android.hardware.SensorEvent; 6 | import android.hardware.SensorEventListener; 7 | import android.hardware.SensorManager; 8 | import android.provider.Settings; 9 | import android.util.Log; 10 | import android.view.accessibility.AccessibilityEvent; 11 | 12 | 13 | public class AppAccessibilityService extends AccessibilityService { 14 | 15 | @Override 16 | public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) { 17 | } 18 | 19 | @Override 20 | public void onInterrupt() { 21 | } 22 | 23 | /** 24 | * 当用户打开无障碍服务时,会执行该方法 25 | */ 26 | @Override 27 | public void onServiceConnected() { 28 | Log.d("ccjy", "无障碍服务启动!!!"); 29 | 30 | AppConfig.init(this); 31 | 32 | addLightSensor(); 33 | GlobalStatus.init(this); 34 | // addTimer(); 35 | } 36 | 37 | private void addLightSensor() { 38 | SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); 39 | Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); 40 | SensorEventListener lightSensorListener = new SensorEventListener() { 41 | @Override 42 | public void onSensorChanged(SensorEvent event) { 43 | // 在这里处理光线传感器事件 44 | GlobalStatus.light = event.values[0]; 45 | } 46 | 47 | @Override 48 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 49 | // 在这里处理传感器精度变化事件 50 | } 51 | }; 52 | sensorManager.registerListener(lightSensorListener, lightSensor, SensorManager.SENSOR_DELAY_NORMAL); 53 | } 54 | 55 | // private void addTimer() { 56 | // AppAccessibilityService appAccessibilityService = this; 57 | // 58 | // Timer timer = new Timer(); 59 | // TimerTask task = new TimerTask() { 60 | // @Override 61 | // public void run() { 62 | // if (GlobalStatus.isFilterOpenMode() && GlobalStatus.light < GlobalStatus.getHighLightThreshold()) { 63 | // // 滤镜打开模式,以及光照没有超过阈值的情况下,确保滤镜打开 64 | // GlobalStatus.openFilter(); 65 | // } 66 | // } 67 | // }; 68 | // timer.schedule(task, 0, 10000); 69 | // } 70 | 71 | /** 72 | * 获取系统亮度条的 progress 73 | */ 74 | public int getSystemBrightnessProgress() { 75 | int getVal = 0; 76 | try { 77 | getVal = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS); 78 | } catch (Settings.SettingNotFoundException e) { 79 | e.printStackTrace(); 80 | } 81 | 82 | // Log.d("ccjy", String.format( 83 | // "读取到系统亮度条为 %d", 84 | // getVal 85 | // )); 86 | 87 | return getVal; 88 | } 89 | 90 | /** 91 | * 设置系统亮度条 92 | */ 93 | public void setSystemBrightnessProgress(int progress) { 94 | int p = Math.min(AppConfig.SETTING_SCREEN_BRIGHTNESS, Math.max(1, progress)); 95 | Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, p); 96 | } 97 | 98 | /** 99 | * 获取系统亮度条对应的亮度; 100 | * 记系统亮度条值为 p, 取值 1-128 的整数 101 | * // ??? b = log2(p)/7 102 | * 103 | * @return brightness 104 | */ 105 | public float getSystemBrightness() { 106 | float p = getSystemBrightnessProgress(); 107 | // float b = (float) (Math.log(p) / Math.log(2)) / 7; 108 | // b = Math.max(b, 0); 109 | // return b; 110 | return p / ((float) AppConfig.SETTING_SCREEN_BRIGHTNESS); 111 | } 112 | 113 | /** 114 | * 通过系统亮度条对应的亮度,设置系统亮度条 115 | */ 116 | public void setSystemBrightnessProgressByBrightness(float brightness) { 117 | 118 | float b = Math.min(1f, Math.max(0f, brightness)); 119 | int bv = (int) (b * AppConfig.SETTING_SCREEN_BRIGHTNESS + 0.5f); 120 | setSystemBrightnessProgress(bv); 121 | 122 | // Log.d("ccjy", String.format( 123 | // "setSystemBrightnessProgress: 设置系统亮度条对应的亮度为 %.1f %%, 注入系统亮度条 %d", 124 | // brightness * 100, bv 125 | // )); 126 | } 127 | 128 | public boolean isReady() { 129 | return Settings.System.canWrite(this); 130 | } 131 | 132 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.util.Log; 7 | 8 | import com.google.gson.Gson; 9 | import com.google.gson.reflect.TypeToken; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.util.ArrayList; 15 | 16 | @SuppressLint("StaticFieldLeak") 17 | public class AppConfig { 18 | 19 | // 一些常数 20 | /** 21 | * 手机屏幕的最大亮度,单位为 nit 22 | */ 23 | public static final float MAX_SCREEN_LIGHT = 500f; 24 | 25 | /** 26 | * Settings.System.SCREEN_BRIGHTNESS 相关的值 27 | * 安卓系统取值是 0-255 28 | * MIUI取值是 0-128 29 | */ 30 | public static final int SETTING_SCREEN_BRIGHTNESS = getSettingScreenBrightness(); 31 | 32 | private static String getSystemProperty(String prop) { 33 | try { 34 | Process p = Runtime.getRuntime().exec("getprop " + prop); 35 | try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024)) { 36 | return input.readLine(); 37 | } finally { 38 | p.destroy(); 39 | } 40 | } catch (IOException e) { 41 | return null; 42 | } 43 | } 44 | 45 | private static int getSettingScreenBrightness() { 46 | String miui = getSystemProperty("ro.miui.ui.version.name"); 47 | if (miui != null && !miui.isEmpty()) { 48 | Log.d("ccjy", "检测到 MIUI 系统"); 49 | return 128; 50 | } 51 | Log.d("ccjy", "检测到非 MIUI 系统"); 52 | return 255; 53 | } 54 | 55 | /** 56 | * 亮度调节系数,与亮度调节算法有关,取值 [0,1] 57 | */ 58 | public static final float BRIGHTNESS_ADJUSTMENT_FACTOR = 0.71f; 59 | 60 | 61 | 62 | 63 | // 默认配置 64 | private static final float default_highLightThreshold = 5000f; 65 | private static final float default_lowLightThreshold = 5f; 66 | private static final float default_minHardwareBrightness = 0.5f; 67 | private static final float default_maxFilterOpacity = 0.9f; 68 | private static final float default_brightnessAdjustmentIncreaseTolerance = 0.04f; 69 | private static final float default_brightnessAdjustmentDecreaseTolerance = 0.21f; 70 | private static final boolean default_filterOpenMode = true; 71 | private static final boolean default_intelligentBrightnessOpenMode = true; 72 | private static final boolean default_hideInMultitaskingInterface = true; 73 | 74 | private static Context context = null; 75 | private static SharedPreferences shared = null; 76 | private static SharedPreferences.Editor editor = null; 77 | // 配置 78 | private static float highLightThreshold; 79 | /** 80 | * 低光照阈值,单位 lux 81 | */ 82 | private static float lowLightThreshold; 83 | private static float minHardwareBrightness; 84 | private static float maxFilterOpacity; 85 | /** 86 | * 亮度调节容差,与亮度调节算法有关 87 | */ 88 | private static float brightnessAdjustmentIncreaseTolerance; 89 | private static float brightnessAdjustmentDecreaseTolerance; 90 | /** 91 | * 列表内元素为 [light,brightness] 92 | */ 93 | private static ArrayList brightnessPointList; 94 | private static boolean filterOpenMode; 95 | private static boolean intelligentBrightnessOpenMode; 96 | private static boolean hideInMultitaskingInterface; 97 | /** 98 | * 临时控制模式 99 | * 用户测试亮度的效果时,或屏幕截图时,为 true ,此时不会自动改变屏幕亮度 100 | */ 101 | private static boolean tempControlMode = false; 102 | 103 | 104 | public static float getHighLightThreshold() { 105 | return highLightThreshold; 106 | } 107 | 108 | public static void setHighLightThreshold(float hlt) { 109 | highLightThreshold = hlt; 110 | editor.putFloat("highLightThreshold", highLightThreshold); 111 | editor.apply(); 112 | } 113 | public static float getLowLightThreshold() { 114 | return lowLightThreshold; 115 | } 116 | 117 | public static void setLowLightThreshold(float lowLightThreshold) { 118 | AppConfig.lowLightThreshold = lowLightThreshold; 119 | editor.putFloat("lowLightThreshold", AppConfig.lowLightThreshold); 120 | editor.apply(); 121 | } 122 | 123 | public static float getMinHardwareBrightness() { 124 | return minHardwareBrightness; 125 | } 126 | 127 | public static void setMinHardwareBrightness(float mhb) { 128 | minHardwareBrightness = mhb; 129 | editor.putFloat("minHardwareBrightness", minHardwareBrightness); 130 | editor.apply(); 131 | } 132 | 133 | public static float getMaxFilterOpacity() { 134 | return maxFilterOpacity; 135 | } 136 | 137 | public static void setMaxFilterOpacity(float mfo) { 138 | maxFilterOpacity = mfo; 139 | editor.putFloat("maxFilterOpacity", maxFilterOpacity); 140 | editor.apply(); 141 | } 142 | 143 | public static float getBrightnessAdjustmentIncreaseTolerance() { 144 | return brightnessAdjustmentIncreaseTolerance; 145 | } 146 | public static void setBrightnessAdjustmentIncreaseTolerance(float brightnessAdjustmentIncreaseTolerance) { 147 | AppConfig.brightnessAdjustmentIncreaseTolerance = brightnessAdjustmentIncreaseTolerance; 148 | editor.putFloat("brightnessAdjustmentIncreaseTolerance", brightnessAdjustmentIncreaseTolerance); 149 | editor.apply(); 150 | } 151 | 152 | public static float getBrightnessAdjustmentDecreaseTolerance() { 153 | return brightnessAdjustmentDecreaseTolerance; 154 | } 155 | 156 | public static void setBrightnessAdjustmentDecreaseTolerance(float brightnessAdjustmentDecreaseTolerance) { 157 | AppConfig.brightnessAdjustmentDecreaseTolerance = brightnessAdjustmentDecreaseTolerance; 158 | editor.putFloat("brightnessAdjustmentDecreaseTolerance", brightnessAdjustmentDecreaseTolerance); 159 | editor.apply(); 160 | } 161 | 162 | private static void sortBrightnessPointList() { 163 | brightnessPointList.sort((a, b) -> Float.compare(a[0], b[0])); 164 | } 165 | 166 | private static void loadDefaultBrightnessPointList() { 167 | clearBrightnessPointList(); 168 | float t = 1.514159f; 169 | addBrightnessPoint(0, 0f / t); 170 | addBrightnessPoint(10, 0.1f / t); 171 | addBrightnessPoint(20, 0.2f / t); 172 | addBrightnessPoint(40, 0.3f / t); 173 | addBrightnessPoint(80, 0.4f / t); 174 | addBrightnessPoint(160, 0.5f / t); 175 | addBrightnessPoint(300, 0.6f / t); 176 | addBrightnessPoint(600, 0.71f / t); 177 | addBrightnessPoint(1000, 0.82f / t); 178 | addBrightnessPoint(1500, 0.95f / t); 179 | addBrightnessPoint(getHighLightThreshold(), 1f); 180 | syncBrightnessPointList(); 181 | } 182 | 183 | private static void syncBrightnessPointList() { 184 | editor.putString("brightnessPointList", new Gson().toJson(brightnessPointList)); 185 | editor.apply(); 186 | } 187 | 188 | public static ArrayList getBrightnessPointList() { 189 | sortBrightnessPointList(); 190 | return brightnessPointList; 191 | } 192 | 193 | public static void clearBrightnessPointList() { 194 | brightnessPointList.clear(); 195 | } 196 | 197 | public static void addBrightnessPoint(float light, float brightness) { 198 | float[] floatArray = new float[]{light, brightness}; 199 | brightnessPointList.add(floatArray); 200 | sortBrightnessPointList(); 201 | syncBrightnessPointList(); 202 | } 203 | 204 | public static void delBrightnessPoint(float light, float brightness) { 205 | float[] floatArray = new float[]{light, brightness}; 206 | 207 | for (int i = 0; i < brightnessPointList.size(); i++) { 208 | float[] arr = brightnessPointList.get(i); 209 | if (arr.length == 2 && arr[0] == floatArray[0] && arr[1] == floatArray[1]) { 210 | brightnessPointList.remove(i); 211 | break; 212 | } 213 | } 214 | syncBrightnessPointList(); 215 | } 216 | 217 | public static boolean isFilterOpenMode() { 218 | return filterOpenMode; 219 | } 220 | 221 | public static void setFilterOpenMode(boolean filterOpenMode) { 222 | if (GlobalStatus.isReady()) { 223 | AppConfig.filterOpenMode = filterOpenMode; 224 | if (!filterOpenMode) { 225 | GlobalStatus.closeFilter(); 226 | setIntelligentBrightnessOpenMode(false); 227 | } 228 | } else { 229 | AppConfig.filterOpenMode = false; 230 | } 231 | editor.putBoolean("filterOpenMode", filterOpenMode); 232 | editor.apply(); 233 | } 234 | 235 | public static boolean isIntelligentBrightnessOpenMode() { 236 | return intelligentBrightnessOpenMode; 237 | } 238 | 239 | public static void setIntelligentBrightnessOpenMode(boolean intelligentBrightnessOpenMode) { 240 | if (GlobalStatus.isReady() && isFilterOpenMode()) { 241 | AppConfig.intelligentBrightnessOpenMode = intelligentBrightnessOpenMode; 242 | } else { 243 | AppConfig.intelligentBrightnessOpenMode = false; 244 | } 245 | editor.putBoolean("intelligentBrightnessOpenMode", intelligentBrightnessOpenMode); 246 | editor.apply(); 247 | } 248 | 249 | public static boolean isHideInMultitaskingInterface() { 250 | return hideInMultitaskingInterface; 251 | } 252 | 253 | public static void setHideInMultitaskingInterface(boolean hideInMultitaskingInterface) { 254 | AppConfig.hideInMultitaskingInterface = hideInMultitaskingInterface; 255 | editor.putBoolean("hideInMultitaskingInterface", hideInMultitaskingInterface); 256 | editor.apply(); 257 | } 258 | 259 | public static boolean isTempControlMode() { 260 | return tempControlMode; 261 | } 262 | 263 | public static void setTempControlMode(boolean tempControlMode) { 264 | AppConfig.tempControlMode = tempControlMode; 265 | } 266 | 267 | public static void init(Context c) { 268 | context = c; 269 | shared = c.getSharedPreferences("share", Context.MODE_PRIVATE); 270 | editor = shared.edit(); 271 | 272 | setTempControlMode(false); 273 | 274 | highLightThreshold = shared.getFloat("highLightThreshold", default_highLightThreshold); 275 | lowLightThreshold = shared.getFloat("lowLightThreshold", default_lowLightThreshold); 276 | maxFilterOpacity = shared.getFloat("maxFilterOpacity", default_maxFilterOpacity); 277 | minHardwareBrightness = shared.getFloat("minHardwareBrightness", default_minHardwareBrightness); 278 | brightnessAdjustmentIncreaseTolerance = shared.getFloat("brightnessAdjustmentIncreaseTolerance", default_brightnessAdjustmentIncreaseTolerance); 279 | brightnessAdjustmentDecreaseTolerance = shared.getFloat("brightnessAdjustmentDecreaseTolerance", default_brightnessAdjustmentDecreaseTolerance); 280 | 281 | if (shared.contains("brightnessPointList")) { 282 | brightnessPointList = new Gson().fromJson( 283 | shared.getString("brightnessPointList", ""), new TypeToken>() { 284 | }.getType() 285 | ); 286 | } else { 287 | brightnessPointList = new ArrayList(); 288 | loadDefaultBrightnessPointList(); 289 | } 290 | 291 | filterOpenMode = shared.getBoolean("filterOpenMode", default_filterOpenMode); 292 | intelligentBrightnessOpenMode = shared.getBoolean("intelligentBrightnessOpenMode", default_intelligentBrightnessOpenMode); 293 | hideInMultitaskingInterface = shared.getBoolean("hideInMultitaskingInterface", default_hideInMultitaskingInterface); 294 | 295 | } 296 | 297 | public static void loadDefaultConfig() { 298 | setFilterOpenMode(default_filterOpenMode); 299 | setIntelligentBrightnessOpenMode(default_intelligentBrightnessOpenMode); 300 | setHideInMultitaskingInterface(default_hideInMultitaskingInterface); 301 | setBrightnessAdjustmentIncreaseTolerance(default_brightnessAdjustmentIncreaseTolerance); 302 | setBrightnessAdjustmentDecreaseTolerance(default_brightnessAdjustmentDecreaseTolerance); 303 | 304 | setHighLightThreshold(default_highLightThreshold); 305 | setLowLightThreshold(default_lowLightThreshold); 306 | setMaxFilterOpacity(default_maxFilterOpacity); 307 | setMinHardwareBrightness(default_minHardwareBrightness); 308 | 309 | loadDefaultBrightnessPointList(); 310 | } 311 | 312 | 313 | 314 | } 315 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/BrightnessManager.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.provider.Settings; 6 | import android.util.Log; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Timer; 10 | import java.util.TimerTask; 11 | 12 | /** 13 | * 2 * 2 * 5 一共 20 种状态; 14 | * 光照改变、系统亮度条被用户拉动、锁屏后开屏 一共三个事件; 15 | */ 16 | public class BrightnessManager { 17 | 18 | Context context; 19 | 20 | private float currentLight; 21 | private boolean isLightChanged; 22 | private float currentSystemBrightness; 23 | private boolean isSystemBrightnessChanged; 24 | private float keepenBrightness = 0; 25 | 26 | private IntelligentBrightnessState intelligentBrightnessState; 27 | 28 | public BrightnessManager(Context c) { 29 | context = c; 30 | intelligentBrightnessState = IntelligentBrightnessState.SMOOTH_LIGHT; 31 | currentLight = GlobalStatus.light; 32 | isLightChanged = true; 33 | currentSystemBrightness = GlobalStatus.getSystemBrightness(); 34 | isSystemBrightnessChanged = true; 35 | // brightnessManageLoop(); 36 | addTimer(); 37 | } 38 | 39 | public float calculateBrightnessByLight(float light) { 40 | 41 | ArrayList brightnessPointList = AppConfig.getBrightnessPointList(); 42 | 43 | if (light > AppConfig.getHighLightThreshold()) { 44 | return 1f; 45 | } 46 | 47 | float brightness = 0; 48 | for (int i = 0; i < brightnessPointList.size() - 1; i++) { 49 | float[] p0 = brightnessPointList.get(i); 50 | float[] p1 = brightnessPointList.get(i + 1); 51 | 52 | if (p0[0] <= light && light <= p1[0]) { 53 | if (Float.compare(p0[0], p1[0]) == 0) { 54 | brightness = p0[1]; 55 | } else { 56 | brightness = p0[1] + ((p1[1] - p0[1]) / (p1[0] - p0[0])) * (light - p0[0]); 57 | } 58 | break; 59 | } 60 | } 61 | return brightness; 62 | } 63 | 64 | /** 65 | * 设置屏幕实际亮度 66 | * 当屏幕滤镜模式开时,调用屏幕滤镜设置亮度 67 | */ 68 | public void setBrightness(float brightness) { 69 | if (GlobalStatus.getFilterOpacity() < 0f) { 70 | return; 71 | } 72 | 73 | // 实际亮度 = 硬件亮度 * ( 1 - 不透明度 )^2 74 | // 不透明度 = 1 - sqrt( 实际亮度 / 硬件亮度 ) 75 | 76 | float sb; 77 | float fo; 78 | 79 | if (brightness > AppConfig.getMinHardwareBrightness()) { 80 | sb = brightness; 81 | fo = 0; 82 | } else { 83 | sb = AppConfig.getMinHardwareBrightness(); 84 | fo = 1f - (float) Math.sqrt(Math.max(0f, brightness / sb)); 85 | 86 | if (fo > AppConfig.getMaxFilterOpacity()) { 87 | fo = AppConfig.getMaxFilterOpacity(); 88 | } 89 | } 90 | 91 | // Log.d("ccjy", String.format( 92 | // "已知最小硬件亮度 %.1f %%, 最大滤镜不透明度 %.1f %%, 要求设置实际亮度 %.1f %%", 93 | // GlobalStatus.getMinHardwareBrightness() * 100, GlobalStatus.getMaxFilterOpacity() * 100, brightness * 100 94 | // )); 95 | 96 | // Log.d("ccjy", String.format( 97 | // "计算得需设置硬件亮度 %.1f %%, 滤镜不透明度 %.1f %%", 98 | // sb * 100, fo * 100 99 | // )); 100 | 101 | GlobalStatus.setHardwareBrightness(sb); 102 | GlobalStatus.setFilterOpacity(fo); 103 | } 104 | 105 | private void addTimer() { 106 | Timer timer = new Timer(); 107 | TimerTask task = new TimerTask() { 108 | @Override 109 | public void run() { 110 | 111 | if (AppConfig.isTempControlMode()) { 112 | return; 113 | } 114 | 115 | if (Float.compare(currentSystemBrightness, GlobalStatus.getSystemBrightness()) != 0) { 116 | // 说明用户调整了状态栏的亮度条 117 | Log.d("ccjy", String.format("使用者修改系统亮度条为 %.1f %%", GlobalStatus.getSystemBrightness() * 100)); 118 | isSystemBrightnessChanged = true; 119 | currentSystemBrightness = GlobalStatus.getSystemBrightness(); 120 | } 121 | 122 | if (Float.compare(currentLight, GlobalStatus.light) != 0) { 123 | // 传感器检测的光照改变 124 | isLightChanged = true; 125 | currentLight = GlobalStatus.light; 126 | } 127 | 128 | if (AppConfig.isFilterOpenMode()) { 129 | brightnessManageLoop(); 130 | } 131 | 132 | isSystemBrightnessChanged = false; 133 | isLightChanged = false; 134 | } 135 | }; 136 | 137 | timer.schedule(task, 0, 200); 138 | } 139 | 140 | 141 | /** 142 | * 在屏幕滤镜开启的情况下,应该被调用 143 | */ 144 | private void brightnessManageLoop() { 145 | if (AppConfig.isIntelligentBrightnessOpenMode()) { 146 | // 智能亮度开 147 | switch (intelligentBrightnessState) { 148 | case SMOOTH_LIGHT: 149 | // SMOOTH_LIGHT 状态下,根据光照计算亮度 150 | 151 | float bset = calculateBrightnessByLight(GlobalStatus.light); 152 | 153 | if (isSystemBrightnessChanged) { 154 | // 反馈用户调节 155 | 156 | // 与自动调节相反 157 | float btoler_high = bset + AppConfig.getBrightnessAdjustmentDecreaseTolerance(); 158 | float btoler_low = bset - AppConfig.getBrightnessAdjustmentIncreaseTolerance(); 159 | float userb = currentSystemBrightness; 160 | if (userb < btoler_low) { 161 | // 用户调节亮度过低 162 | keepenBrightness = bset - (bset - btoler_low) * AppConfig.BRIGHTNESS_ADJUSTMENT_FACTOR; 163 | } else if (userb > btoler_high) { 164 | // 用户调节亮度过高 165 | keepenBrightness = bset + (btoler_high - bset) * AppConfig.BRIGHTNESS_ADJUSTMENT_FACTOR; 166 | } else { 167 | // 用户调节亮度处于容差之内 168 | keepenBrightness = userb; 169 | } 170 | } else { 171 | float btoler_high = keepenBrightness + AppConfig.getBrightnessAdjustmentIncreaseTolerance(); 172 | float btoler_low = keepenBrightness - AppConfig.getBrightnessAdjustmentDecreaseTolerance(); 173 | 174 | // 用来稳定亮度 175 | if ((bset < btoler_low) || (bset > btoler_high)) { 176 | keepenBrightness = (bset + keepenBrightness) / 2; 177 | } 178 | } 179 | 180 | if ((GlobalStatus.light < AppConfig.getLowLightThreshold()) && (GlobalStatus.getSystemBrightnessProgress() <= 1)) { 181 | // 暗光模式 182 | keepenBrightness = 0f; 183 | } 184 | 185 | GlobalStatus.openFilter(); 186 | GlobalStatus.setBrightness(keepenBrightness); 187 | GlobalStatus.setSystemBrightnessProgressByBrightness(keepenBrightness); 188 | 189 | if (GlobalStatus.light > AppConfig.getHighLightThreshold()) { 190 | // 光照过高,转到系统自动亮度 191 | GlobalStatus.closeFilter(); 192 | openSystemAutoBrightnessMode(); 193 | intelligentBrightnessState = IntelligentBrightnessState.HIGH_LIGHT; 194 | } else { 195 | // 确保关闭系统自动亮度 196 | closeSystemAutoBrightnessMode(); 197 | } 198 | break; 199 | case HIGH_LIGHT: 200 | // HIGH_LIGHT 下,系统自动亮度,当光照过低,转到 SMOOTH_LIGHT 201 | if (GlobalStatus.light <= AppConfig.getHighLightThreshold()) { 202 | // 关闭系统自动亮度 203 | closeSystemAutoBrightnessMode(); 204 | GlobalStatus.openFilter(); 205 | intelligentBrightnessState = IntelligentBrightnessState.SMOOTH_LIGHT; 206 | } 207 | 208 | if (GlobalStatus.getSystemBrightness() < 1f) { 209 | GlobalStatus.setSystemBrightnessProgressByBrightness(1f); 210 | } 211 | break; 212 | } 213 | } else { 214 | // 智能亮度关 215 | 216 | keepenBrightness = GlobalStatus.getSystemBrightness(); 217 | 218 | if (GlobalStatus.light > AppConfig.getHighLightThreshold()) { 219 | // 阳光模式 220 | // 开启自动亮度 221 | GlobalStatus.closeFilter(); 222 | openSystemAutoBrightnessMode(); 223 | } else { 224 | // 关闭自动亮度 225 | closeSystemAutoBrightnessMode(); 226 | GlobalStatus.openFilter(); 227 | } 228 | 229 | if ((GlobalStatus.light < AppConfig.getLowLightThreshold()) && (GlobalStatus.getSystemBrightnessProgress() <= 1)) { 230 | // 暗光模式 231 | keepenBrightness = 0f; 232 | } 233 | 234 | GlobalStatus.setBrightness(keepenBrightness); 235 | } 236 | } 237 | 238 | private void openSystemAutoBrightnessMode() { 239 | try { 240 | // 获取系统亮度模式设置 241 | ContentResolver contentResolver = context.getContentResolver(); 242 | int mode = Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE); 243 | if (mode != Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { 244 | // 自动亮度未开启,开启自动亮度 245 | Settings.System.putInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); 246 | Log.d("ccjy", "开启自动亮度"); 247 | } 248 | } catch (Settings.SettingNotFoundException e) { 249 | Log.d("ccjy", "开启自动亮度失败"); 250 | } 251 | } 252 | 253 | private void closeSystemAutoBrightnessMode() { 254 | try { 255 | ContentResolver contentResolver = context.getContentResolver(); 256 | int mode = Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE); 257 | if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { 258 | // 自动亮度已开启,关闭自动亮度 259 | Settings.System.putInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); 260 | Log.d("ccjy", "关闭自动亮度"); 261 | } 262 | } catch (Settings.SettingNotFoundException e) { 263 | Log.d("ccjy", "关闭自动亮度失败"); 264 | } 265 | } 266 | 267 | 268 | private enum IntelligentBrightnessState { 269 | SMOOTH_LIGHT, 270 | HIGH_LIGHT, 271 | } 272 | 273 | 274 | } 275 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/FilterViewManager.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.PixelFormat; 6 | import android.os.Handler; 7 | import android.os.Looper; 8 | import android.view.View; 9 | import android.view.WindowManager; 10 | 11 | public class FilterViewManager { 12 | 13 | private final Context context; 14 | private final WindowManager windowManager; 15 | private final WindowManager.LayoutParams layoutParams; 16 | private final FilterView filterView; 17 | /** 18 | * 滤镜处于开启状态,为 true 19 | */ 20 | public boolean isOpen; 21 | private float alpha = 0f; 22 | private float hardwareBrightness = 0f; 23 | 24 | public FilterViewManager(Context c) { 25 | // 这里假设传入的 Context 有无障碍权限,后面的代码不对无障碍权限进行检验 26 | 27 | isOpen = false; 28 | context = c; 29 | windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 30 | layoutParams = new WindowManager.LayoutParams(); 31 | filterView = new FilterView(context); 32 | 33 | layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; 34 | // width 和 height 尽可能大,从而覆盖屏幕 35 | layoutParams.width = 4000; 36 | layoutParams.height = 4000; 37 | layoutParams.format = PixelFormat.TRANSLUCENT; 38 | layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 39 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | 40 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 41 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | 42 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 43 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 44 | } 45 | 46 | public void open() { 47 | if (isOpen) { 48 | return; 49 | } 50 | 51 | new Handler(Looper.getMainLooper()).post(() -> { 52 | // 在UI线程中更新UI组件 53 | if (!isOpen) { 54 | windowManager.addView(filterView, layoutParams); 55 | setAlpha(alpha); 56 | setHardwareBrightness(hardwareBrightness); 57 | isOpen = true; 58 | } 59 | }); 60 | } 61 | 62 | public void close() { 63 | if (!isOpen) { 64 | return; 65 | } 66 | 67 | new Handler(Looper.getMainLooper()).post(() -> { 68 | // 在UI线程中更新UI组件 69 | if (isOpen) { 70 | windowManager.removeView(filterView); 71 | isOpen = false; 72 | } 73 | }); 74 | } 75 | 76 | public float getAlpha() { 77 | if (isOpen) { 78 | return alpha; 79 | } else { 80 | return -1f; 81 | } 82 | } 83 | 84 | public void setAlpha(float alpha) { 85 | if (!isOpen) { 86 | return; 87 | } 88 | 89 | if (Float.compare(this.alpha, alpha) == 0) { 90 | return; 91 | } 92 | 93 | new Handler(Looper.getMainLooper()).post(() -> { 94 | // 在UI线程中更新UI组件 95 | if (isOpen) { 96 | float a = Math.min(1f, Math.max(0f, alpha)); 97 | filterView.setAlpha(a); 98 | this.alpha = a; 99 | } 100 | }); 101 | } 102 | 103 | public float getHardwareBrightness() { 104 | if (isOpen) { 105 | return hardwareBrightness; 106 | } else { 107 | return -1f; 108 | } 109 | } 110 | 111 | public void setHardwareBrightness(float brightness) { 112 | if (!isOpen) { 113 | return; 114 | } 115 | 116 | if (Float.compare(this.hardwareBrightness, brightness) == 0) { 117 | return; 118 | } 119 | 120 | new Handler(Looper.getMainLooper()).post(() -> { 121 | // 在UI线程中更新UI组件 122 | if (isOpen) { 123 | float b = Math.min(1f, Math.max(0f, brightness)); 124 | // layoutParams.screenBrightness 会覆盖系统亮度设置 125 | layoutParams.screenBrightness = b; 126 | windowManager.updateViewLayout(filterView, layoutParams); 127 | hardwareBrightness = b; 128 | } 129 | }); 130 | } 131 | 132 | private static class FilterView extends View { 133 | 134 | public FilterView(Context context) { 135 | super(context); 136 | setBackgroundColor(Color.BLACK); 137 | setAlpha(0f); 138 | } 139 | 140 | @Override 141 | public void setAlpha(float alpha) { 142 | super.setAlpha(alpha); 143 | invalidate(); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/GlobalStatus.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter; 2 | 3 | 4 | import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; 5 | 6 | import android.accessibilityservice.AccessibilityService; 7 | import android.accessibilityservice.GestureDescription; 8 | import android.annotation.SuppressLint; 9 | import android.graphics.Path; 10 | import android.os.Handler; 11 | import android.os.Looper; 12 | import android.util.Log; 13 | 14 | @SuppressLint("StaticFieldLeak") 15 | public class GlobalStatus { 16 | 17 | /** 18 | * 当前环境光照,由 AppAccessibilityService 持续赋值 19 | */ 20 | public static float light = 0; 21 | 22 | private static AppAccessibilityService appAccessibilityService = null; 23 | private static FilterViewManager filterViewManager = null; 24 | private static BrightnessManager brightnessManager = null; 25 | 26 | 27 | /** 28 | * Global 初始化 29 | */ 30 | public static void init(AppAccessibilityService a) { 31 | appAccessibilityService = a; 32 | filterViewManager = new FilterViewManager(a); 33 | brightnessManager = new BrightnessManager(a); 34 | 35 | if (AppConfig.isFilterOpenMode()) { 36 | openFilter(); 37 | } 38 | } 39 | 40 | /** 41 | * 当无障碍服务已打开,应用所需的各权限都满足后,返回 true 42 | */ 43 | public static boolean isReady() { 44 | return isAccessibility() && appAccessibilityService.isReady(); 45 | } 46 | 47 | /** 48 | * 当无障碍服务已打开, 返回 true 49 | */ 50 | public static boolean isAccessibility() { 51 | return (appAccessibilityService != null); 52 | } 53 | 54 | public static void openFilter() { 55 | if (isReady() && filterViewManager.getHardwareBrightness() < 0f) { 56 | filterViewManager.open(); 57 | } 58 | } 59 | 60 | public static void closeFilter() { 61 | if (isReady() && filterViewManager.getHardwareBrightness() >= 0) { 62 | filterViewManager.close(); 63 | } 64 | } 65 | 66 | public static float calculateBrightnessByLight(float light) { 67 | return brightnessManager.calculateBrightnessByLight(light); 68 | } 69 | 70 | /** 71 | * 获取屏幕滤镜的不透明度 72 | * 滤镜未打开,返回 -1 73 | * 未初始化,返回 -2 74 | */ 75 | public static float getFilterOpacity() { 76 | if (filterViewManager != null) { 77 | return filterViewManager.getAlpha(); 78 | } else { 79 | return -2f; 80 | } 81 | } 82 | 83 | /** 84 | * 设置不透明度; 85 | * 86 | * @param alpha 0 表示完全透明,1 表示完全不透明 87 | */ 88 | public static void setFilterOpacity(float alpha) { 89 | if (isReady()) { 90 | filterViewManager.setAlpha(alpha); 91 | } 92 | } 93 | 94 | /** 95 | * 获取屏幕滤镜的亮度 96 | * 滤镜未打开,返回 -1 97 | * 未初始化,返回 -2 98 | */ 99 | public static float getHardwareBrightness() { 100 | if (filterViewManager != null) { 101 | return filterViewManager.getHardwareBrightness(); 102 | } else { 103 | return -2f; 104 | } 105 | } 106 | 107 | /** 108 | * 利用 layoutParams.screenBrightness 设置硬件亮度,覆盖状态栏系统亮度条的效果; 109 | * 110 | * @param brightness [0,1] 111 | */ 112 | public static void setHardwareBrightness(float brightness) { 113 | if (isReady()) { 114 | filterViewManager.setHardwareBrightness(brightness); 115 | } 116 | } 117 | 118 | 119 | /** 120 | * 获取系统亮度条对应的亮度 121 | */ 122 | public static float getSystemBrightness() { 123 | if (isReady()) { 124 | return appAccessibilityService.getSystemBrightness(); 125 | } else { 126 | return 0; 127 | } 128 | } 129 | 130 | /** 131 | * 获取系统亮度条值 132 | * 与 AppConfig.SETTING_SCREEN_BRIGHTNESS 有关 133 | */ 134 | public static int getSystemBrightnessProgress() { 135 | if (isReady()) { 136 | return appAccessibilityService.getSystemBrightnessProgress(); 137 | } else { 138 | return 0; 139 | } 140 | } 141 | 142 | /** 143 | * 设置系统亮度条 144 | * 与 AppConfig.SETTING_SCREEN_BRIGHTNESS 有关 145 | */ 146 | public static void setSystemBrightnessProgress(int progress) { 147 | if (isReady()) { 148 | appAccessibilityService.setSystemBrightnessProgress(progress); 149 | } 150 | } 151 | 152 | /** 153 | * 通过亮度值控制状态栏系统亮度条 154 | * 当屏幕滤镜打开时,状态栏系统亮度条会被滤镜的亮度值覆盖,不会改变硬件亮度 155 | * 156 | * @param brightness [0,1] 157 | */ 158 | public static void setSystemBrightnessProgressByBrightness(float brightness) { 159 | if (isReady()) { 160 | appAccessibilityService.setSystemBrightnessProgressByBrightness(brightness); 161 | } 162 | } 163 | 164 | /** 165 | * 获取当前屏幕亮度 166 | */ 167 | public static float getBrightness() { 168 | if (getFilterOpacity() >= 0) { 169 | // 实际亮度 = 硬件亮度 * ( 1 - 不透明度 )^2 170 | return getHardwareBrightness() * (float) Math.pow((1 - getFilterOpacity()), 2); 171 | } else { 172 | return getSystemBrightness(); 173 | } 174 | } 175 | 176 | 177 | /** 178 | * 设置实际亮度,会自动计算滤镜不透明度和硬件亮度,并设置 179 | * 180 | * @param brightness [0,1] 181 | */ 182 | public static void setBrightness(float brightness) { 183 | if (isReady() && filterViewManager.isOpen) { 184 | brightnessManager.setBrightness(brightness); 185 | } 186 | } 187 | 188 | /** 189 | * 触发屏幕截图 190 | * 先模拟上划操作,从而上拉任务栏 191 | * 延时后打开系统截图服务 192 | */ 193 | public static void triggerScreenCap() { 194 | if (appAccessibilityService != null) { 195 | 196 | // 模拟上划操作,从而上拉任务栏 197 | Path path = new Path(); 198 | path.moveTo(540, 2300); 199 | path.lineTo(540, 2000); 200 | 201 | GestureDescription gestureDescription = new GestureDescription.Builder().addStroke( 202 | new GestureDescription.StrokeDescription(path, 0, 50 203 | )).build(); 204 | 205 | Log.d("ccjy", "模拟上滑操作"); 206 | long time1 = System.currentTimeMillis(); 207 | appAccessibilityService.dispatchGesture(gestureDescription, new AccessibilityService.GestureResultCallback() { 208 | @Override 209 | public void onCompleted(GestureDescription gestureDescription) { 210 | super.onCompleted(gestureDescription); 211 | // 手势事件执行完成后的回调方法 212 | long time2 = System.currentTimeMillis(); 213 | long diffMillis = time2 - time1; 214 | Log.d("ccjy", String.format( 215 | "手势事件执行用了 %d ms", diffMillis 216 | )); 217 | } 218 | }, null); 219 | 220 | // 延时后打开系统截图服务 221 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 222 | Log.d("ccjy", "开启系统截图服务"); 223 | appAccessibilityService.performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT); 224 | }, 500); 225 | } 226 | } 227 | 228 | 229 | } 230 | 231 | 232 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter; 2 | 3 | import android.app.ActivityManager; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.os.Looper; 11 | import android.util.Log; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.widget.Toast; 15 | 16 | import androidx.appcompat.app.AppCompatActivity; 17 | 18 | import com.cjyyxn.screenfilter.ui.MainUI; 19 | import com.cjyyxn.screenfilter.ui.PreparatoryActivity; 20 | 21 | public class MainActivity extends AppCompatActivity { 22 | 23 | private MainUI mainUI; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | 30 | AppConfig.init(this); 31 | 32 | Log.d("ccjy", "MainActivity created"); 33 | mainUI = new MainUI(this); 34 | } 35 | 36 | @Override 37 | public void onResume() { 38 | super.onResume(); 39 | 40 | all_judge(); 41 | 42 | mainUI.onResume(); 43 | } 44 | 45 | @Override 46 | protected void onPause() { 47 | super.onPause(); 48 | 49 | if (AppConfig.isHideInMultitaskingInterface()) { 50 | try { 51 | ActivityManager service = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); 52 | for (ActivityManager.AppTask task : service.getAppTasks()) { 53 | if (task.getTaskInfo().taskId == getTaskId()) { 54 | task.setExcludeFromRecents(true); 55 | new Handler(Looper.getMainLooper()).post(() -> { 56 | Toast.makeText(this, "多任务界面隐藏成功", Toast.LENGTH_SHORT).show(); 57 | }); 58 | } 59 | } 60 | } catch (Exception ex) { 61 | ex.printStackTrace(); 62 | new Handler(Looper.getMainLooper()).post(() -> { 63 | Toast.makeText(this, "多任务界面隐藏失败", Toast.LENGTH_SHORT).show(); 64 | }); 65 | } 66 | } 67 | 68 | mainUI.onPause(); 69 | 70 | } 71 | 72 | public void all_judge() { 73 | SharedPreferences shared = getSharedPreferences("share", Context.MODE_PRIVATE); 74 | if (!shared.getBoolean("agreePrivacyPolicy", false)) { 75 | 76 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 77 | builder.setTitle("滤镜护眼防频闪 隐私政策"); 78 | 79 | LayoutInflater inflater = getLayoutInflater(); 80 | View view = inflater.inflate(R.layout.dialog_privacy_policy, null); 81 | builder.setView(view); 82 | 83 | builder.setPositiveButton("同意", (dialog, which) -> { 84 | SharedPreferences.Editor editor = shared.edit(); 85 | editor.putBoolean("agreePrivacyPolicy", true); 86 | editor.apply(); 87 | ready_judge(); 88 | }); 89 | 90 | builder.setNegativeButton("取消", (dialog, which) -> { 91 | finish(); 92 | }); 93 | 94 | 95 | AlertDialog dialog = builder.create(); 96 | dialog.setOnCancelListener(dialogInterface -> { 97 | finish(); 98 | }); 99 | 100 | dialog.show(); 101 | } else { 102 | Log.d("ccjy", "已同意隐私政策"); 103 | ready_judge(); 104 | } 105 | } 106 | 107 | private void ready_judge() { 108 | if (!GlobalStatus.isReady()) { 109 | new Handler(Looper.getMainLooper()).post(() -> { 110 | Toast.makeText(this, "未设置必须的权限", Toast.LENGTH_SHORT).show(); 111 | }); 112 | startActivity(new Intent(this, PreparatoryActivity.class)); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/quicksetting/QuickSettingFilter.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.quicksetting; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.service.quicksettings.Tile; 6 | import android.service.quicksettings.TileService; 7 | import android.widget.Toast; 8 | 9 | import com.cjyyxn.screenfilter.AppConfig; 10 | 11 | import java.util.Timer; 12 | import java.util.TimerTask; 13 | 14 | 15 | public class QuickSettingFilter extends TileService { 16 | 17 | @Override 18 | public void onCreate() { 19 | super.onCreate(); 20 | 21 | TimerTask task = new TimerTask() { 22 | @Override 23 | public void run() { 24 | try { 25 | int toggleState = getQsTile().getState(); 26 | 27 | if (AppConfig.isFilterOpenMode()) { 28 | // 滤镜打开模式 29 | if (toggleState == Tile.STATE_INACTIVE) { 30 | // 如果磁贴为未激活状态,改成激活 31 | getQsTile().setState(Tile.STATE_ACTIVE); 32 | } 33 | } else { 34 | // 滤镜关闭模式 35 | if (toggleState == Tile.STATE_ACTIVE) { 36 | // 如果磁贴为激活状态,改成未激活 37 | getQsTile().setState(Tile.STATE_INACTIVE); 38 | } 39 | } 40 | getQsTile().updateTile(); 41 | } catch (Exception e) { 42 | // e.printStackTrace(); 43 | } 44 | } 45 | }; 46 | new Timer().schedule(task, 0, 2000); 47 | } 48 | 49 | // 点击的时候 50 | @Override 51 | public void onClick() { 52 | 53 | int toggleState = getQsTile().getState(); 54 | 55 | // 如果磁贴为激活状态 被点击 则动作为关闭滤镜 56 | if (toggleState == Tile.STATE_ACTIVE) { 57 | AppConfig.setFilterOpenMode(false); 58 | new Handler(Looper.getMainLooper()).post(() -> { 59 | Toast.makeText(this, "关闭屏幕滤镜", Toast.LENGTH_SHORT).show(); 60 | }); 61 | getQsTile().setState(Tile.STATE_INACTIVE); 62 | } 63 | // 如果磁贴为未激活状态 被点击 则动作为开启滤镜 64 | else if (toggleState == Tile.STATE_INACTIVE) { 65 | AppConfig.setFilterOpenMode(true); 66 | new Handler(Looper.getMainLooper()).post(() -> { 67 | Toast.makeText(this, "打开屏幕滤镜", Toast.LENGTH_SHORT).show(); 68 | }); 69 | getQsTile().setState(Tile.STATE_ACTIVE); 70 | } 71 | getQsTile().updateTile(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/quicksetting/QuickSettingIntelligentBrightness.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.quicksetting; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.service.quicksettings.Tile; 6 | import android.service.quicksettings.TileService; 7 | import android.widget.Toast; 8 | 9 | import com.cjyyxn.screenfilter.AppConfig; 10 | 11 | import java.util.Timer; 12 | import java.util.TimerTask; 13 | 14 | public class QuickSettingIntelligentBrightness extends TileService { 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | 20 | TimerTask task = new TimerTask() { 21 | @Override 22 | public void run() { 23 | try { 24 | int toggleState = getQsTile().getState(); 25 | 26 | if (AppConfig.isIntelligentBrightnessOpenMode()) { 27 | // 智能亮度打开模式 28 | if (toggleState == Tile.STATE_INACTIVE) { 29 | // 如果磁贴为未激活状态,改成激活 30 | getQsTile().setState(Tile.STATE_ACTIVE); 31 | } 32 | } else { 33 | // 智能亮度关闭模式 34 | if (toggleState == Tile.STATE_ACTIVE) { 35 | // 如果磁贴为激活状态,改成未激活 36 | getQsTile().setState(Tile.STATE_INACTIVE); 37 | } 38 | } 39 | getQsTile().updateTile(); 40 | } catch (Exception e) { 41 | // e.printStackTrace(); 42 | } 43 | } 44 | }; 45 | new Timer().schedule(task, 0, 2000); 46 | } 47 | 48 | // 点击的时候 49 | @Override 50 | public void onClick() { 51 | 52 | int toggleState = getQsTile().getState(); 53 | 54 | // 如果磁贴为激活状态 被点击 则动作为关闭智能亮度 55 | if (toggleState == Tile.STATE_ACTIVE) { 56 | AppConfig.setIntelligentBrightnessOpenMode(false); 57 | new Handler(Looper.getMainLooper()).post(() -> { 58 | Toast.makeText(this, "关闭智能亮度", Toast.LENGTH_SHORT).show(); 59 | }); 60 | getQsTile().setState(Tile.STATE_INACTIVE); 61 | } 62 | // 如果磁贴为未激活状态 被点击 则动作为开启智能亮度 63 | else if (toggleState == Tile.STATE_INACTIVE) { 64 | AppConfig.setIntelligentBrightnessOpenMode(true); 65 | new Handler(Looper.getMainLooper()).post(() -> { 66 | Toast.makeText(this, "打开智能亮度", Toast.LENGTH_SHORT).show(); 67 | }); 68 | getQsTile().setState(Tile.STATE_ACTIVE); 69 | } 70 | getQsTile().updateTile(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/quicksetting/QuickSettingScreenShot.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.quicksetting; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.service.quicksettings.TileService; 6 | 7 | import com.cjyyxn.screenfilter.AppConfig; 8 | import com.cjyyxn.screenfilter.GlobalStatus; 9 | 10 | public class QuickSettingScreenShot extends TileService { 11 | @Override 12 | public void onClick() { 13 | AppConfig.setTempControlMode(true); 14 | GlobalStatus.closeFilter(); 15 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 16 | GlobalStatus.triggerScreenCap(); 17 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 18 | AppConfig.setTempControlMode(false); 19 | }, 1300); 20 | }, 400); 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/ui/BrightnessPointActivity.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.ui; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.AlertDialog; 5 | import android.content.DialogInterface; 6 | import android.os.Bundle; 7 | import android.os.Handler; 8 | import android.os.Looper; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.Button; 13 | import android.widget.LinearLayout; 14 | import android.widget.SeekBar; 15 | import android.widget.TextView; 16 | 17 | import androidx.appcompat.app.AppCompatActivity; 18 | 19 | import com.androidplot.xy.BoundaryMode; 20 | import com.androidplot.xy.LineAndPointFormatter; 21 | import com.androidplot.xy.PanZoom; 22 | import com.androidplot.xy.SimpleXYSeries; 23 | import com.androidplot.xy.StepMode; 24 | import com.androidplot.xy.XYPlot; 25 | import com.androidplot.xy.XYSeries; 26 | import com.cjyyxn.screenfilter.AppConfig; 27 | import com.cjyyxn.screenfilter.GlobalStatus; 28 | import com.cjyyxn.screenfilter.R; 29 | import com.cjyyxn.screenfilter.utils.TimerControl; 30 | 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | import java.util.stream.Collectors; 34 | 35 | @SuppressLint({"DefaultLocale", "InflateParams"}) 36 | public class BrightnessPointActivity extends AppCompatActivity { 37 | 38 | private XYPlot plot; 39 | 40 | private View dialogBrightnessView; 41 | 42 | private SeekBar sb_dialog_brightness_point_light; 43 | private float brightness_point_dialog_light; 44 | private TextView tv_dialog_brightness_point_light; 45 | private TextView tv_dialog_sensor_light; 46 | private SeekBar sb_dialog_brightness_point_brightness; 47 | private float brightness_point_dialog_brightness; // [0,1] 48 | private TextView tv_dialog_brightness_point_brightness; 49 | private Button bt_list_brightness_point_add; 50 | 51 | private LinearLayout ll_list_brightness_point_container; 52 | private TimerControl timerControl; 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | setContentView(R.layout.activity_brightness_point); 58 | 59 | bt_list_brightness_point_add = findViewById(R.id.bt_list_brightness_point_add); 60 | bt_list_brightness_point_add.setOnClickListener(new View.OnClickListener() { 61 | @Override 62 | public void onClick(View view) { 63 | openAddBrightnessPointDialog(); 64 | } 65 | }); 66 | 67 | showPlot(); 68 | initBrightnessDialog(); 69 | setTimer(); 70 | addBrightnessPointListView(); 71 | } 72 | 73 | @Override 74 | protected void onResume() { 75 | super.onResume(); 76 | timerControl.start(0, 200); 77 | } 78 | 79 | @Override 80 | protected void onPause() { 81 | super.onPause(); 82 | timerControl.stop(); 83 | } 84 | 85 | private void showPlot() { 86 | 87 | plot = (XYPlot) findViewById(R.id.plot); 88 | plot.setDomainBoundaries(-20, AppConfig.getHighLightThreshold(), BoundaryMode.FIXED); 89 | plot.setRangeBoundaries(0, 100, BoundaryMode.FIXED); 90 | 91 | plot.setDomainStep(StepMode.SUBDIVIDE, 11); 92 | plot.setRangeStep(StepMode.SUBDIVIDE, 11); 93 | 94 | // 能够移动和缩放 95 | // PanZoom.attach(plot); 96 | PanZoom.attach(plot, PanZoom.Pan.HORIZONTAL, PanZoom.Zoom.STRETCH_HORIZONTAL); 97 | plot.getOuterLimits().set(0, AppConfig.getHighLightThreshold(), -1, 100); 98 | 99 | plot.getLegend().setVisible(false); 100 | 101 | ArrayList bpl = AppConfig.getBrightnessPointList(); 102 | 103 | List list1 = bpl.stream().map(arr -> arr[0]).collect(Collectors.toList()); 104 | List list2 = bpl.stream().map(arr -> arr[1] * 100).collect(Collectors.toList()); 105 | 106 | XYSeries series = new SimpleXYSeries(list1, list2, "xxx"); 107 | 108 | LineAndPointFormatter seriesFormat = 109 | new LineAndPointFormatter(this, R.xml.line_point_formatter_with_labels); 110 | 111 | 112 | plot.addSeries(series, seriesFormat); 113 | 114 | } 115 | 116 | private void updatePlot() { 117 | plot.clear(); 118 | ArrayList bpl = AppConfig.getBrightnessPointList(); 119 | 120 | List list1 = bpl.stream().map(arr -> arr[0]).collect(Collectors.toList()); 121 | List list2 = bpl.stream().map(arr -> arr[1] * 100).collect(Collectors.toList()); 122 | 123 | XYSeries series = new SimpleXYSeries(list1, list2, "xxx"); 124 | 125 | LineAndPointFormatter seriesFormat = 126 | new LineAndPointFormatter(this, R.xml.line_point_formatter_with_labels); 127 | 128 | 129 | plot.addSeries(series, seriesFormat); 130 | plot.redraw(); 131 | } 132 | 133 | 134 | /** 135 | * 初始化光照-亮度对应点对话框 136 | */ 137 | 138 | private void initBrightnessDialog() { 139 | dialogBrightnessView = LayoutInflater.from(this).inflate(R.layout.dialog_brightness_point, null); 140 | sb_dialog_brightness_point_light = dialogBrightnessView.findViewById(R.id.sb_dialog_brightness_point_light); 141 | tv_dialog_brightness_point_light = dialogBrightnessView.findViewById(R.id.tv_dialog_brightness_point_light); 142 | tv_dialog_sensor_light = dialogBrightnessView.findViewById(R.id.tv_dialog_sensor_light); 143 | sb_dialog_brightness_point_brightness = dialogBrightnessView.findViewById(R.id.sb_dialog_brightness_point_brightness); 144 | tv_dialog_brightness_point_brightness = dialogBrightnessView.findViewById(R.id.tv_dialog_brightness_point_brightness); 145 | 146 | // 设置光照的拖动条 147 | sb_dialog_brightness_point_light.setMax(light2progress(AppConfig.getHighLightThreshold())); 148 | sb_dialog_brightness_point_light.setMin(0); 149 | sb_dialog_brightness_point_light.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 150 | @Override 151 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 152 | // 在进度条改变时执行的代码 153 | brightness_point_dialog_light = progress2light(progress); 154 | tv_dialog_brightness_point_light.setText(String.format("%.1f lux", brightness_point_dialog_light)); 155 | GlobalStatus.setBrightness(GlobalStatus.calculateBrightnessByLight(brightness_point_dialog_light)); 156 | } 157 | 158 | @Override 159 | public void onStartTrackingTouch(SeekBar seekBar) { 160 | // 在开始拖动进度条时执行的代码 161 | AppConfig.setTempControlMode(true); 162 | } 163 | 164 | @Override 165 | public void onStopTrackingTouch(SeekBar seekBar) { 166 | // 在停止拖动进度条时执行的代码 167 | AppConfig.setTempControlMode(false); 168 | } 169 | }); 170 | 171 | // 设置亮度的拖动条 172 | sb_dialog_brightness_point_brightness.setMax(1000); 173 | sb_dialog_brightness_point_brightness.setMin(0); 174 | sb_dialog_brightness_point_brightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 175 | @Override 176 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 177 | // 在进度条改变时执行的代码 178 | brightness_point_dialog_brightness = ((float) progress) / 1000f; 179 | tv_dialog_brightness_point_brightness.setText(String.format("%.1f %%", brightness_point_dialog_brightness * 100)); 180 | GlobalStatus.setBrightness(brightness_point_dialog_brightness); 181 | } 182 | 183 | @Override 184 | public void onStartTrackingTouch(SeekBar seekBar) { 185 | // 在开始拖动进度条时执行的代码 186 | AppConfig.setTempControlMode(true); 187 | } 188 | 189 | @Override 190 | public void onStopTrackingTouch(SeekBar seekBar) { 191 | // 在停止拖动进度条时执行的代码 192 | AppConfig.setTempControlMode(false); 193 | } 194 | }); 195 | 196 | } 197 | 198 | private void openAddBrightnessPointDialog() { 199 | // 重复打开对话框时,避免报错 200 | ViewGroup parent = (ViewGroup) dialogBrightnessView.getParent(); 201 | if (parent != null) { 202 | parent.removeView(dialogBrightnessView); 203 | } 204 | 205 | float gl = GlobalStatus.light; 206 | tv_dialog_brightness_point_light.setText(String.format("%.1f lux", gl)); 207 | sb_dialog_brightness_point_light.setProgress(light2progress(gl)); 208 | 209 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 210 | builder.setView(dialogBrightnessView); 211 | 212 | builder.setTitle("添加光照-亮度对应点"); 213 | builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { 214 | @Override 215 | public void onClick(DialogInterface dialog, int which) { 216 | AppConfig.addBrightnessPoint(brightness_point_dialog_light, brightness_point_dialog_brightness); 217 | updateBrightnessPointListView(); 218 | updatePlot(); 219 | } 220 | }); 221 | builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { 222 | @Override 223 | public void onClick(DialogInterface dialog, int which) { 224 | } 225 | }); 226 | 227 | AlertDialog dialog = builder.create(); 228 | dialog.show(); 229 | } 230 | 231 | private void openModifyBrightnessPointDialog(float light, float brightness) { 232 | // 重复打开对话框时,避免报错 233 | ViewGroup parent = (ViewGroup) dialogBrightnessView.getParent(); 234 | if (parent != null) { 235 | parent.removeView(dialogBrightnessView); 236 | } 237 | 238 | tv_dialog_brightness_point_light.setText(String.format("%.1f lux", light)); 239 | sb_dialog_brightness_point_light.setProgress(light2progress(light)); 240 | tv_dialog_brightness_point_brightness.setText( 241 | String.format("%.1f %%", brightness * 100)); 242 | sb_dialog_brightness_point_brightness.setProgress( 243 | (int) (brightness * 1000) 244 | ); 245 | 246 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 247 | builder.setView(dialogBrightnessView); 248 | 249 | builder.setTitle("修改光照-亮度对应点"); 250 | builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { 251 | @Override 252 | public void onClick(DialogInterface dialog, int which) { 253 | AppConfig.delBrightnessPoint(light, brightness); 254 | AppConfig.addBrightnessPoint(brightness_point_dialog_light, brightness_point_dialog_brightness); 255 | updateBrightnessPointListView(); 256 | updatePlot(); 257 | } 258 | }); 259 | builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { 260 | @Override 261 | public void onClick(DialogInterface dialog, int which) { 262 | } 263 | }); 264 | 265 | AlertDialog dialog = builder.create(); 266 | dialog.show(); 267 | } 268 | 269 | private void setTimer() { 270 | timerControl = new TimerControl(() -> { 271 | new Handler(Looper.getMainLooper()).post(() -> { 272 | // 在UI线程中更新UI组件 273 | // Log.d("ccjy", "更新 BrightnessPointActivityUI"); 274 | if (tv_dialog_sensor_light != null) { 275 | tv_dialog_sensor_light.setText(String.format("%.1f lux", GlobalStatus.light)); 276 | } 277 | }); 278 | }); 279 | } 280 | 281 | /** 282 | * 拖动条区间[0,10000],映射至 [0,highLightThreshold] 283 | * L=(2^(p/10000)-1)^3*H 284 | */ 285 | private float progress2light(int progress) { 286 | float p = (float) progress; 287 | float H = AppConfig.getHighLightThreshold(); 288 | float L = (float) Math.pow((Math.pow(2, p / 10000) - 1), 3) * H; 289 | 290 | return L; 291 | } 292 | 293 | /** 294 | * 光照区间 [0,highLightThreshold],映射至拖动条区间[0,10000] 295 | * p=log2((L/H)^(1/3)+1)*10000 296 | */ 297 | private int light2progress(float light) { 298 | float L = light; 299 | float H = AppConfig.getHighLightThreshold(); 300 | double p = (Math.log(Math.pow(L / H, 1.f / 3.f) + 1) / Math.log(2)) * 10000; 301 | return (int) p; 302 | } 303 | 304 | private void addBrightnessPointListView() { 305 | ll_list_brightness_point_container = findViewById(R.id.ll_list_brightness_point_container); 306 | 307 | ArrayList bpl = AppConfig.getBrightnessPointList(); 308 | 309 | for (int i = 0; i < bpl.size(); i++) { 310 | float[] arr = bpl.get(i); 311 | addBrightnessPointView(arr[0], arr[1]); 312 | } 313 | 314 | } 315 | 316 | private void updateBrightnessPointListView() { 317 | 318 | ll_list_brightness_point_container.removeAllViews(); 319 | 320 | ArrayList bpl = AppConfig.getBrightnessPointList(); 321 | 322 | for (int i = 0; i < bpl.size(); i++) { 323 | float[] arr = bpl.get(i); 324 | addBrightnessPointView(arr[0], arr[1]); 325 | } 326 | } 327 | 328 | private void addBrightnessPointView(float light, float brightness) { 329 | if (Float.compare(light, 0) == 0 && Float.compare(brightness, 0) == 0) { 330 | return; 331 | } else if (Float.compare(light, AppConfig.getHighLightThreshold()) == 0 && Float.compare(brightness, 1f) == 0) { 332 | return; 333 | } 334 | 335 | LinearLayout cloneLayout = (LinearLayout) LayoutInflater.from(this) 336 | .inflate(R.layout.list_brightness_point, null); 337 | 338 | TextView tvl = cloneLayout.findViewById(R.id.tv_list_brightness_point_light); 339 | tvl.setText(String.format("光照: %.1f lux", light)); 340 | 341 | TextView tvb = cloneLayout.findViewById(R.id.tv_list_brightness_point_brightness); 342 | tvb.setText(String.format("亮度: %.1f %%", brightness * 100)); 343 | 344 | // 删除按钮 345 | Button btd = cloneLayout.findViewById(R.id.bt_list_brightness_point_delete); 346 | btd.setOnClickListener(new View.OnClickListener() { 347 | @Override 348 | public void onClick(View view) { 349 | AppConfig.delBrightnessPoint(light, brightness); 350 | updateBrightnessPointListView(); 351 | updatePlot(); 352 | } 353 | }); 354 | 355 | // 修改按钮 356 | Button btm = cloneLayout.findViewById(R.id.bt_list_brightness_point_modify); 357 | btm.setOnClickListener(new View.OnClickListener() { 358 | @Override 359 | public void onClick(View view) { 360 | openModifyBrightnessPointDialog(light, brightness); 361 | } 362 | }); 363 | ll_list_brightness_point_container.addView(cloneLayout); 364 | } 365 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/ui/DebugActivity.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.ui; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.view.LayoutInflater; 8 | import android.widget.CompoundButton; 9 | import android.widget.LinearLayout; 10 | import android.widget.SeekBar; 11 | import android.widget.Switch; 12 | import android.widget.TextView; 13 | 14 | import androidx.appcompat.app.AppCompatActivity; 15 | 16 | import com.cjyyxn.screenfilter.AppConfig; 17 | import com.cjyyxn.screenfilter.GlobalStatus; 18 | import com.cjyyxn.screenfilter.R; 19 | import com.cjyyxn.screenfilter.utils.TimerControl; 20 | 21 | import java.util.function.Consumer; 22 | import java.util.function.Function; 23 | 24 | @SuppressLint("DefaultLocale") 25 | public class DebugActivity extends AppCompatActivity { 26 | 27 | private TextView tv_debug_run_info; 28 | @SuppressLint("UseSwitchCompatOrMaterialCode") 29 | private Switch sw_debug_temp_control; 30 | private LinearLayout ll_list_debug_seekbar_control; 31 | private TimerControl timerControl; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_debug); 37 | 38 | tv_debug_run_info = findViewById(R.id.tv_debug_run_info); 39 | sw_debug_temp_control = findViewById(R.id.sw_debug_temp_control); 40 | ll_list_debug_seekbar_control = findViewById(R.id.ll_list_debug_seekbar_control); 41 | 42 | setUI(); 43 | setTimer(); 44 | } 45 | 46 | @Override 47 | protected void onResume() { 48 | super.onResume(); 49 | timerControl.start(0, 200); 50 | } 51 | 52 | @Override 53 | protected void onPause() { 54 | super.onPause(); 55 | timerControl.stop(); 56 | } 57 | 58 | private void setUI() { 59 | sw_debug_temp_control.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 60 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 61 | AppConfig.setTempControlMode(isChecked); 62 | } 63 | }); 64 | 65 | addSeekBarControl( 66 | "屏幕亮度", 0, 100, 67 | (P) -> String.format("%d %%", P), 68 | (P) -> GlobalStatus.setBrightness(((float) P) * (1f / 100f)) 69 | ); 70 | addSeekBarControl( 71 | "滤镜不透明度", 0, 100, 72 | (P) -> String.format("%d %%", P), 73 | (P) -> GlobalStatus.setFilterOpacity(((float) P) * (1f / 100f)) 74 | ); 75 | addSeekBarControl( 76 | "硬件亮度", 0, 100, 77 | (P) -> String.format("%d %%", P), 78 | (P) -> GlobalStatus.setHardwareBrightness(((float) P) * (1f / 100f)) 79 | ); 80 | addSeekBarControl( 81 | "用亮度设置状态栏亮度条", 0, 100, 82 | (P) -> String.format("%d %%", P), 83 | (P) -> GlobalStatus.setSystemBrightnessProgressByBrightness(((float) P) * (1f / 100f)) 84 | ); 85 | } 86 | 87 | 88 | private void addSeekBarControl( 89 | String name, 90 | int minP, 91 | int maxP, 92 | Function tv_set, 93 | Consumer onPChanged 94 | ) { 95 | LinearLayout cloneLayout = (LinearLayout) LayoutInflater.from(this) 96 | .inflate(R.layout.seekbar_control, null); 97 | 98 | TextView tv_control_name = cloneLayout.findViewById(R.id.tv_control_name); 99 | SeekBar sb_control = cloneLayout.findViewById(R.id.sb_control); 100 | TextView tv_control_set = cloneLayout.findViewById(R.id.tv_control_set); 101 | 102 | tv_control_name.setText(name); 103 | 104 | sb_control.setMin(minP); 105 | sb_control.setMax(maxP); 106 | sb_control.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 107 | @Override 108 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 109 | tv_control_set.setText(tv_set.apply(progress)); 110 | onPChanged.accept(progress); 111 | } 112 | 113 | @Override 114 | public void onStartTrackingTouch(SeekBar seekBar) { 115 | } 116 | 117 | @Override 118 | public void onStopTrackingTouch(SeekBar seekBar) { 119 | } 120 | }); 121 | 122 | ll_list_debug_seekbar_control.addView(cloneLayout); 123 | } 124 | 125 | private void setTimer() { 126 | timerControl = new TimerControl(() -> { 127 | new Handler(Looper.getMainLooper()).post(() -> { 128 | // 在UI线程中更新UI组件 129 | tv_debug_run_info.setText(""); 130 | tv_debug_run_info.append("应用运行信息:"); 131 | tv_debug_run_info.append(String.format( 132 | "\n当前环境光照 %.2f lux, 屏幕亮度 %.2f %%", 133 | GlobalStatus.light, GlobalStatus.getBrightness() * 100 134 | )); 135 | tv_debug_run_info.append(String.format( 136 | "\n当前系统亮度条 int 值 %d, 系统亮度 %.2f %%", 137 | GlobalStatus.getSystemBrightnessProgress(), GlobalStatus.getSystemBrightness() * 100 138 | )); 139 | tv_debug_run_info.append(String.format( 140 | "\n当前滤镜亮度 %.2f %%, 滤镜不透明度 %.2f %%", 141 | GlobalStatus.getHardwareBrightness() * 100, GlobalStatus.getFilterOpacity() * 100 142 | )); 143 | tv_debug_run_info.append(String.format( 144 | "\n当前屏幕实际亮度(估计值) %.2f nit", GlobalStatus.getBrightness() * AppConfig.MAX_SCREEN_LIGHT 145 | )); 146 | }); 147 | }); 148 | } 149 | 150 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/ui/MainUI.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.ui; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.widget.LinearLayout; 8 | import android.widget.TextView; 9 | import android.widget.Toast; 10 | 11 | import com.cjyyxn.screenfilter.AppConfig; 12 | import com.cjyyxn.screenfilter.GlobalStatus; 13 | import com.cjyyxn.screenfilter.MainActivity; 14 | import com.cjyyxn.screenfilter.R; 15 | import com.cjyyxn.screenfilter.utils.CombinationControl; 16 | import com.cjyyxn.screenfilter.utils.TimerControl; 17 | 18 | @SuppressLint({"DefaultLocale", "UseSwitchCompatOrMaterialCode"}) 19 | public class MainUI { 20 | 21 | private final MainActivity mainActivity; 22 | private final TextView tv_main_light; 23 | private final TextView tv_main_brightness; 24 | private final LinearLayout ll0_list_main; 25 | private final CombinationControl combinationControl; 26 | private TimerControl timerControl; 27 | 28 | public MainUI(MainActivity act) { 29 | mainActivity = act; 30 | 31 | tv_main_light = mainActivity.findViewById(R.id.tv_main_light); 32 | tv_main_brightness = mainActivity.findViewById(R.id.tv_main_brightness); 33 | ll0_list_main = mainActivity.findViewById(R.id.ll0_list_main); 34 | 35 | combinationControl = new CombinationControl(ll0_list_main, mainActivity); 36 | 37 | setUI(); 38 | setTimer(); 39 | } 40 | 41 | private void setUI() { 42 | 43 | combinationControl.addSwitchControl( 44 | "屏幕滤镜开关", 45 | (buttonView, isChecked) -> AppConfig.setFilterOpenMode(isChecked), 46 | (sw) -> sw.setChecked(AppConfig.isFilterOpenMode()) 47 | ); 48 | combinationControl.addSwitchControl( 49 | "智能亮度开关", 50 | (buttonView, isChecked) -> AppConfig.setIntelligentBrightnessOpenMode(isChecked), 51 | (sw) -> sw.setChecked(AppConfig.isIntelligentBrightnessOpenMode()) 52 | ); 53 | combinationControl.addSwitchControl( 54 | "在多任务界面隐藏", 55 | (buttonView, isChecked) -> AppConfig.setHideInMultitaskingInterface(isChecked), 56 | (sw) -> sw.setChecked(AppConfig.isHideInMultitaskingInterface()) 57 | ); 58 | 59 | combinationControl.addLine(); 60 | 61 | /** 62 | * 主界面用户设置屏幕亮度 1,128 -> 0,1 63 | * 应与系统状态栏亮度同步 64 | */ 65 | combinationControl.addSeekBarControl( 66 | "屏幕亮度设置", 1, AppConfig.SETTING_SCREEN_BRIGHTNESS, 67 | (P) -> String.format("%.0f %%", GlobalStatus.getSystemBrightness() * 100), 68 | (sb, P, fromUser) -> { 69 | if (fromUser) { 70 | GlobalStatus.setSystemBrightnessProgress(P); 71 | GlobalStatus.setBrightness(GlobalStatus.getSystemBrightness()); 72 | } 73 | }, 74 | (sb) -> AppConfig.setTempControlMode(true), 75 | (sb) -> AppConfig.setTempControlMode(false), 76 | (sb) -> sb.setProgress(GlobalStatus.getSystemBrightnessProgress()) 77 | ); 78 | combinationControl.addSeekBarControl( 79 | "亮光模式阈值", 1, 100, 80 | (P) -> String.format("%.0f lux", AppConfig.getHighLightThreshold()), 81 | (sb, P, fromUser) -> { 82 | if (fromUser) { 83 | AppConfig.setHighLightThreshold(((float) P) * (100f)); 84 | } 85 | }, 86 | (sb) -> CombinationControl.pass(), 87 | (sb) -> CombinationControl.pass(), 88 | (sb) -> sb.setProgress((int) (AppConfig.getHighLightThreshold() / 100f + 0.5)) 89 | ); 90 | combinationControl.addSeekBarControl( 91 | "暗光模式阈值", 0, 20, 92 | (P) -> String.format("%.0f lux", AppConfig.getLowLightThreshold()), 93 | (sb, P, fromUser) -> { 94 | if (fromUser) { 95 | AppConfig.setLowLightThreshold((float) P); 96 | } 97 | }, 98 | (sb) -> CombinationControl.pass(), 99 | (sb) -> CombinationControl.pass(), 100 | (sb) -> sb.setProgress((int) (AppConfig.getLowLightThreshold() + 0.5f)) 101 | ); 102 | combinationControl.addSeekBarControl( 103 | "最低硬件亮度", 0, 100, 104 | (P) -> String.format("%.0f %%", AppConfig.getMinHardwareBrightness() * 100), 105 | (sb, P, fromUser) -> { 106 | if (fromUser) { 107 | AppConfig.setMinHardwareBrightness(((float) P) / 100f); 108 | } 109 | }, 110 | (sb) -> CombinationControl.pass(), 111 | (sb) -> CombinationControl.pass(), 112 | (sb) -> sb.setProgress((int) (AppConfig.getMinHardwareBrightness() * 100 + 0.5f)) 113 | ); 114 | combinationControl.addSeekBarControl( 115 | "最高滤镜\n不透明度", 60, 100, 116 | (P) -> String.format("%.0f %%", AppConfig.getMaxFilterOpacity() * 100), 117 | (sb, P, fromUser) -> { 118 | if (fromUser) { 119 | AppConfig.setMaxFilterOpacity(((float) P) / 100f); 120 | } 121 | }, 122 | (sb) -> CombinationControl.pass(), 123 | (sb) -> CombinationControl.pass(), 124 | (sb) -> sb.setProgress((int) (AppConfig.getMaxFilterOpacity() * 100f + 0.5f)) 125 | ); 126 | combinationControl.addSeekBarControl( 127 | "亮度调高容差", 0, 30, 128 | (P) -> String.format("%.2f", AppConfig.getBrightnessAdjustmentIncreaseTolerance()), 129 | (sb, P, fromUser) -> { 130 | if (fromUser) { 131 | AppConfig.setBrightnessAdjustmentIncreaseTolerance(((float) P) / 100f); 132 | } 133 | }, 134 | (sb) -> CombinationControl.pass(), 135 | (sb) -> CombinationControl.pass(), 136 | (sb) -> sb.setProgress((int) (AppConfig.getBrightnessAdjustmentIncreaseTolerance() * 100f + 0.5f)) 137 | ); 138 | combinationControl.addSeekBarControl( 139 | "亮度调低容差", 0, 50, 140 | (P) -> String.format("%.2f", AppConfig.getBrightnessAdjustmentDecreaseTolerance()), 141 | (sb, P, fromUser) -> { 142 | if (fromUser) { 143 | AppConfig.setBrightnessAdjustmentDecreaseTolerance(((float) P) / 100f); 144 | } 145 | }, 146 | (sb) -> CombinationControl.pass(), 147 | (sb) -> CombinationControl.pass(), 148 | (sb) -> sb.setProgress((int) (AppConfig.getBrightnessAdjustmentDecreaseTolerance() * 100f + 0.5f)) 149 | ); 150 | 151 | combinationControl.addLine(); 152 | 153 | combinationControl.addJumpLabel( 154 | "打开准备界面", 155 | () -> mainActivity.startActivity(new Intent(mainActivity, PreparatoryActivity.class)) 156 | ); 157 | combinationControl.addJumpLabel( 158 | "打开使用说明", 159 | () -> mainActivity.startActivity(new Intent(mainActivity, ReadmeActivity.class)) 160 | ); 161 | combinationControl.addJumpLabel( 162 | "打开亮度-光照曲线设置界面", 163 | () -> mainActivity.startActivity(new Intent(mainActivity, BrightnessPointActivity.class)) 164 | ); 165 | combinationControl.addJumpLabel( 166 | "加载默认配置", 167 | () -> { 168 | AppConfig.loadDefaultConfig(); 169 | new Handler(Looper.getMainLooper()).post(() -> { 170 | Toast.makeText(mainActivity, "默认配置加载成功", Toast.LENGTH_SHORT).show(); 171 | }); 172 | } 173 | ); 174 | combinationControl.addJumpLabel( 175 | "打开调试界面", 176 | () -> mainActivity.startActivity(new Intent(mainActivity, DebugActivity.class)) 177 | ); 178 | 179 | } 180 | 181 | private void setTimer() { 182 | timerControl = new TimerControl(() -> { 183 | combinationControl.update(); 184 | 185 | new Handler(Looper.getMainLooper()).post(() -> { 186 | // 在UI线程中更新UI组件 187 | // Log.d("ccjy", "更新 mainUI"); 188 | tv_main_light.setText(String.format("当前环境光照: %.1f lux", GlobalStatus.light)); 189 | tv_main_brightness.setText(String.format("当前屏幕亮度: %.1f %%", GlobalStatus.getBrightness() * 100)); 190 | 191 | }); 192 | }); 193 | } 194 | 195 | public void onResume() { 196 | timerControl.start(0, 200); 197 | } 198 | 199 | public void onPause() { 200 | timerControl.stop(); 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/ui/PreparatoryActivity.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.ui; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.provider.Settings; 7 | import android.widget.Button; 8 | import android.widget.Toast; 9 | 10 | import androidx.appcompat.app.AppCompatActivity; 11 | 12 | import com.cjyyxn.screenfilter.GlobalStatus; 13 | import com.cjyyxn.screenfilter.R; 14 | 15 | public class PreparatoryActivity extends AppCompatActivity { 16 | 17 | private Button pbt0; 18 | private Button pbt1; 19 | private Button pbt2; 20 | private Button pbt3; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_preparatory); 26 | 27 | pbt0 = findViewById(R.id.pbt0); 28 | pbt1 = findViewById(R.id.pbt1); 29 | pbt2 = findViewById(R.id.pbt2); 30 | pbt3 = findViewById(R.id.pbt3); 31 | 32 | pbt0.setOnClickListener(view -> onButton0()); 33 | pbt1.setOnClickListener(view -> onButton1()); 34 | pbt2.setOnClickListener(view -> onButton2()); 35 | pbt3.setOnClickListener(view -> onButton3()); 36 | 37 | // Timer timer = new Timer(); 38 | // TimerTask task = new TimerTask() { 39 | // @Override 40 | // public void run() { 41 | // 42 | // if (GlobalStatus.isAccessibility()) { 43 | // finish(); 44 | // } 45 | // 46 | // } 47 | // }; 48 | // 49 | // // 每隔 0.1秒钟执行一次任务 50 | // timer.schedule(task, 0, 100); 51 | } 52 | 53 | private void onButton0() { 54 | if (GlobalStatus.isAccessibility()) { 55 | Toast.makeText(this, "无障碍已打开", Toast.LENGTH_SHORT).show(); 56 | } else { 57 | try { 58 | startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | Toast.makeText(this, "无障碍设置界面不可用", Toast.LENGTH_SHORT).show(); 62 | } 63 | } 64 | } 65 | 66 | private void onButton1() { 67 | if (!Settings.System.canWrite(this)) { 68 | // 如果权限尚未授予,则请求权限 69 | Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); 70 | intent.setData(Uri.parse("package:" + getPackageName())); 71 | startActivity(intent); 72 | } else { 73 | // 如果权限已经授予,则执行需要权限的操作 74 | Toast.makeText(this, "系统设置权限已开启", Toast.LENGTH_SHORT).show(); 75 | } 76 | 77 | // if (GlobalStatus.isReady()) { 78 | // 79 | // } else { 80 | // Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 81 | // Uri uri = Uri.fromParts("package", "com.cjyyxn.screenfilter", null); 82 | // intent.setData(uri); 83 | // startActivity(intent); 84 | // } 85 | } 86 | 87 | private void onButton2() { 88 | if (GlobalStatus.isReady()) { 89 | finish(); 90 | } else { 91 | Toast.makeText(this, "未设置必须的权限", Toast.LENGTH_SHORT).show(); 92 | } 93 | } 94 | 95 | private void onButton3() { 96 | Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 97 | // Uri uri = Uri.fromParts("package", "com.cjyyxn.screenfilter", null); 98 | intent.setData(Uri.parse("package:" + getPackageName())); 99 | startActivity(intent); 100 | } 101 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/ui/ReadmeActivity.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.ui; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | import com.cjyyxn.screenfilter.R; 8 | 9 | public class ReadmeActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_readme); 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/utils/CombinationControl.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.utils; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.annotation.SuppressLint; 7 | import android.content.Context; 8 | import android.os.Handler; 9 | import android.os.Looper; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.widget.CompoundButton; 13 | import android.widget.LinearLayout; 14 | import android.widget.SeekBar; 15 | import android.widget.Switch; 16 | import android.widget.TextView; 17 | 18 | import androidx.core.content.ContextCompat; 19 | 20 | import com.cjyyxn.screenfilter.R; 21 | 22 | import java.util.ArrayList; 23 | import java.util.function.BiConsumer; 24 | import java.util.function.Consumer; 25 | import java.util.function.Function; 26 | 27 | @SuppressLint("InflateParams") 28 | public class CombinationControl { 29 | 30 | private final LinearLayout linearLayout; 31 | private final Context context; 32 | 33 | private ArrayList sbcList = new ArrayList(); 34 | private ArrayList swcList = new ArrayList(); 35 | 36 | public CombinationControl(LinearLayout ll, Context c) { 37 | linearLayout = ll; 38 | context = c; 39 | } 40 | 41 | public static void pass() { 42 | 43 | } 44 | 45 | public void addSeekBarControl( 46 | String name, int minP, int maxP, 47 | Function tv_set, 48 | TriConsumer onPrChanged, 49 | Consumer onStartTouch, 50 | Consumer onStopTouch, 51 | Consumer updateMethod 52 | ) { 53 | sbcList.add(new SeekBarControl( 54 | linearLayout, context, 55 | name, minP, maxP, 56 | tv_set, 57 | onPrChanged, 58 | onStartTouch, 59 | onStopTouch, 60 | updateMethod 61 | )); 62 | } 63 | 64 | public void addSwitchControl( 65 | String name, 66 | BiConsumer onCheckedChanged, 67 | Consumer updateMethod 68 | ) { 69 | swcList.add(new SwitchControl( 70 | linearLayout, context, 71 | name, 72 | onCheckedChanged, 73 | updateMethod 74 | )); 75 | } 76 | 77 | public void addJumpLabel( 78 | String name, 79 | Runnable on_click 80 | ) { 81 | LinearLayout cloneLayout = (LinearLayout) LayoutInflater.from(context) 82 | .inflate(R.layout.jumplabel_control, null); 83 | 84 | TextView tv_control_name = cloneLayout.findViewById(R.id.tv_control_name); 85 | tv_control_name.setText(name); 86 | cloneLayout.setOnClickListener(new View.OnClickListener() { 87 | @Override 88 | public void onClick(View v) { 89 | // 执行点击动效 90 | ObjectAnimator animator = ObjectAnimator.ofFloat(cloneLayout, "translationX", 0f, 100f); 91 | animator.setDuration(100); 92 | animator.addListener(new AnimatorListenerAdapter() { 93 | @Override 94 | public void onAnimationEnd(Animator animation) { 95 | // 动画结束时进行恢复操作 96 | cloneLayout.setTranslationX(0f); 97 | } 98 | }); 99 | animator.start(); 100 | 101 | on_click.run(); 102 | } 103 | }); 104 | linearLayout.addView(cloneLayout); 105 | } 106 | 107 | public void addLine() { 108 | View view1 = new View(context); 109 | LinearLayout.LayoutParams layoutParams1 = new LinearLayout.LayoutParams( 110 | LinearLayout.LayoutParams.MATCH_PARENT, 16); 111 | linearLayout.addView(view1, layoutParams1); 112 | 113 | View view2 = new View(context); 114 | view2.setBackgroundColor(ContextCompat.getColor(context, android.R.color.darker_gray)); // 设置背景颜色 115 | LinearLayout.LayoutParams layoutParams2 = new LinearLayout.LayoutParams( 116 | LinearLayout.LayoutParams.MATCH_PARENT, 3); 117 | linearLayout.addView(view2, layoutParams2); 118 | 119 | View view3 = new View(context); 120 | LinearLayout.LayoutParams layoutParams3 = new LinearLayout.LayoutParams( 121 | LinearLayout.LayoutParams.MATCH_PARENT, 16); 122 | linearLayout.addView(view3, layoutParams3); 123 | } 124 | 125 | public void update() { 126 | new Handler(Looper.getMainLooper()).post(() -> { 127 | // 在UI线程中更新UI组件 128 | for (int i = 0; i < sbcList.size(); i++) { 129 | sbcList.get(i).update(); 130 | } 131 | for (int i = 0; i < swcList.size(); i++) { 132 | swcList.get(i).update(); 133 | } 134 | }); 135 | } 136 | 137 | private class SeekBarControl { 138 | private final LinearLayout linearLayout; 139 | private final Context context; 140 | private final String name; 141 | private final int minP; 142 | private final int maxP; 143 | private final Function tv_set; 144 | private final TriConsumer onPrChanged; 145 | private final Consumer onStartTouch; 146 | private final Consumer onStopTouch; 147 | 148 | private final Consumer updateMethod; 149 | 150 | private final SeekBar sb_control; 151 | private final TextView tv_control_set; 152 | 153 | public SeekBarControl( 154 | LinearLayout ll, Context c, 155 | String name, int minP, int maxP, 156 | Function tv_set, 157 | TriConsumer onPrChanged, 158 | Consumer onStartTouch, 159 | Consumer onStopTouch, 160 | Consumer updateMethod 161 | ) { 162 | linearLayout = ll; 163 | context = c; 164 | this.name = name; 165 | this.minP = minP; 166 | this.maxP = maxP; 167 | this.tv_set = tv_set; 168 | this.onPrChanged = onPrChanged; 169 | this.onStartTouch = onStartTouch; 170 | this.onStopTouch = onStopTouch; 171 | this.updateMethod = updateMethod; 172 | 173 | LinearLayout cloneLayout = (LinearLayout) LayoutInflater.from(context) 174 | .inflate(R.layout.seekbar_control, null); 175 | 176 | TextView tv_control_name = cloneLayout.findViewById(R.id.tv_control_name); 177 | sb_control = cloneLayout.findViewById(R.id.sb_control); 178 | tv_control_set = cloneLayout.findViewById(R.id.tv_control_set); 179 | 180 | tv_control_name.setText(name); 181 | 182 | sb_control.setMin(minP); 183 | sb_control.setMax(maxP); 184 | sb_control.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 185 | @Override 186 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 187 | onPrChanged.accept(seekBar, progress, fromUser); 188 | tv_control_set.setText(tv_set.apply(progress)); 189 | } 190 | 191 | @Override 192 | public void onStartTrackingTouch(SeekBar seekBar) { 193 | onStartTouch.accept(seekBar); 194 | } 195 | 196 | @Override 197 | public void onStopTrackingTouch(SeekBar seekBar) { 198 | onStopTouch.accept(seekBar); 199 | } 200 | }); 201 | 202 | linearLayout.addView(cloneLayout); 203 | 204 | } 205 | 206 | public void update() { 207 | updateMethod.accept(sb_control); 208 | tv_control_set.setText(tv_set.apply(sb_control.getProgress())); 209 | } 210 | } 211 | 212 | private class SwitchControl { 213 | private final LinearLayout linearLayout; 214 | private final Context context; 215 | private final String name; 216 | 217 | private final BiConsumer onCheckedChanged; 218 | private final Consumer updateMethod; 219 | 220 | private final Switch sw_control; 221 | 222 | public SwitchControl( 223 | LinearLayout ll, Context c, 224 | String name, 225 | BiConsumer onCheckedChanged, 226 | Consumer updateMethod 227 | ) { 228 | linearLayout = ll; 229 | context = c; 230 | this.name = name; 231 | this.onCheckedChanged = onCheckedChanged; 232 | this.updateMethod = updateMethod; 233 | 234 | LinearLayout cloneLayout = (LinearLayout) LayoutInflater.from(context) 235 | .inflate(R.layout.switch_control, null); 236 | 237 | sw_control = cloneLayout.findViewById(R.id.sw_control); 238 | 239 | sw_control.setText(name); 240 | sw_control.setOnCheckedChangeListener(onCheckedChanged::accept); 241 | 242 | linearLayout.addView(cloneLayout); 243 | 244 | } 245 | 246 | public void update() { 247 | updateMethod.accept(sw_control); 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/utils/TimerControl.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.utils; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.Timer; 6 | import java.util.TimerTask; 7 | 8 | public class TimerControl { 9 | private final Runnable timerTask; 10 | private Timer timer = null; 11 | private boolean isRunning = false; 12 | 13 | public TimerControl(Runnable timerTask) { 14 | this.timerTask = timerTask; 15 | isRunning = false; 16 | } 17 | 18 | public void start( 19 | long delay, 20 | long period 21 | ) { 22 | if (!isRunning) { 23 | isRunning = true; 24 | timer = new Timer(); 25 | TimerTask task = new TimerTask() { 26 | @Override 27 | public void run() { 28 | // Log.d("ccjy", "timer 被调用"); 29 | timerTask.run(); 30 | } 31 | }; 32 | timer.schedule(task, delay, period); 33 | } 34 | } 35 | 36 | public void stop() { 37 | if (isRunning && (timer != null)) { 38 | isRunning = false; 39 | timer.cancel(); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/cjyyxn/screenfilter/utils/TriConsumer.java: -------------------------------------------------------------------------------- 1 | package com.cjyyxn.screenfilter.utils; 2 | 3 | @FunctionalInterface 4 | public interface TriConsumer { 5 | void accept(T t, U u, V v); 6 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/app/src/main/res/drawable/filter.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/intelligent_brightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/app/src/main/res/drawable/intelligent_brightness.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjyyx/ScreenFilter/8cbe958db4f5e55c6168e8f5f055857c8e664bc7/app/src/main/res/drawable/screenshot.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_brightness_point.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 22 | 25 | 26 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 |