你的位置:首页 > 操作系统

[操作系统]利用悬浮窗进一步降低屏幕亮度保护眼睛(app based on Android)


项目地址:https://github.com/hwding/make-it-darker

欢迎star、fork以及一切建议和意见!

 

在完全黑暗中看手机眼睛会非常容易疲劳,即使将手机亮度调整到最低也会让自己在完全适应后感到非常刺眼。

利用android的悬浮窗将半透明的黑色layout覆盖在屏幕上进一步降低亮度。

同时通过改变滤镜颜色可以过滤蓝光等等。

出于自身需求给自己写了一个小玩意~

 

效果:

    

 

悬浮窗作为服务启动:

 1 package com.amastigote.darker.service; 2  3 import android.app.Service; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.os.IBinder; 7 import android.support.annotation.Nullable; 8 import android.view.LayoutInflater; 9 import android.view.WindowManager;10 import android.widget.LinearLayout;11 import com.amastigote.darker.R;12 import com.amastigote.darker.model.DarkerSettings;13 14 public class ScreenFilterService extends Service{15   static LinearLayout linearLayout;16   static WindowManager.LayoutParams layoutParams;17   static WindowManager windowManager;18 19   @Override20   public void onCreate() {21     super.onCreate();22     createScreenFilter();23   }24 25   @Override26   public void onDestroy() {27     super.onDestroy();28     if (linearLayout != null)29       windowManager.removeView(linearLayout);30   }31 32   @Nullable33   @Override34   public IBinder onBind(Intent intent) {35     return null;36   }37 38   @SuppressWarnings(value = "all")39   private void createScreenFilter() {40     layoutParams = new WindowManager.LayoutParams();41     windowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);42     layoutParams.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;43     layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE44              | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL45              | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;46     layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;47     layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;48 49     LayoutInflater layoutInflater = LayoutInflater.from(getApplication());50     linearLayout = (LinearLayout) layoutInflater.inflate(R.layout.screen_filter, null);51     windowManager.addView(linearLayout, layoutParams);52     removeScreenFilter();53   }54 55   public static void updateScreenFilter(DarkerSettings darkerSettings) {56     layoutParams.screenBrightness = darkerSettings.getBrightness();57     layoutParams.alpha = darkerSettings.getAlpha();58     windowManager.updateViewLayout(linearLayout, layoutParams);59   }60 61   public static void removeScreenFilter() {62     layoutParams.screenBrightness = DarkerSettings.BRIGHTNESS_AUTO;63     layoutParams.alpha = DarkerSettings.ALPHA_MINIMUM;64     windowManager.updateViewLayout(linearLayout, layoutParams);65   }66 }

ScreenFilterService.java

此处应注意,给layoutParams设置属性type时需要用到类型TYPE_PRIORITY_PHONE,可以覆盖一切屏幕上的内容。

关于此项类型需要特定的permission申请,此处不表,见下。

screenBrightness为悬浮窗自身亮度,这里默认为最低0.0F

alpha为悬浮窗layout的透明度,默认为0.4F,最高设定为0.8F防止全黑导致无法操作。

update...()方法用于读入用户在app控制面版上的配置并更新filter

remove...()方法并没有移除filter而是将filter的亮度恢复为自动并设置为全透明,方便下一次启动filter。

 

 1 package com.amastigote.darker.activity; 2  3 import android.content.Intent; 4 import android.net.Uri; 5 import android.os.Build; 6 import android.os.Bundle; 7 import android.provider.Settings; 8 import android.support.v7.app.AppCompatActivity; 9 import android.support.v7.widget.Toolbar; 10 import android.view.Menu; 11 import android.view.MenuItem; 12 import android.view.View; 13 import android.view.animation.AlphaAnimation; 14 import android.widget.Button; 15 import android.widget.Switch; 16 import android.widget.Toast; 17 import android.widget.ToggleButton; 18 import com.amastigote.darker.R; 19 import com.amastigote.darker.model.DarkerSettings; 20 import com.amastigote.darker.service.ScreenFilterService; 21 import com.rtugeek.android.colorseekbar.ColorSeekBar; 22 import io.feeeei.circleseekbar.CircleSeekBar; 23  24 public class MainActivity extends AppCompatActivity { 25   DarkerSettings currentDarkerSettings = new DarkerSettings(); 26   CircleSeekBar circleSeekBar_brightness; 27   CircleSeekBar circleSeekBar_alpha; 28   ColorSeekBar colorSeekBar; 29   Switch aSwitch; 30   Intent intent; 31  32   @Override 33   protected void onDestroy() { 34     if (intent != null) 35       stopService(intent); 36     super.onDestroy(); 37   } 38  39   @Override 40   protected void onCreate(Bundle savedInstanceState) { 41     super.onCreate(savedInstanceState); 42     setContentView(R.layout.activity_main); 43     Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 44     toolbar.setTitle("make it darker!"); 45     setSupportActionBar(toolbar); 46     DarkerSettings.initializeContext(getApplicationContext()); 47  48     checkPermissions(); 49  50     circleSeekBar_brightness = (CircleSeekBar) findViewById(R.id.cp_brightness_circleSeekBar); 51     circleSeekBar_alpha = (CircleSeekBar) findViewById(R.id.cp_alpha_circleSeekBar); 52     colorSeekBar = (ColorSeekBar) findViewById(R.id.cp_colorSeekBar); 53     aSwitch = (Switch) findViewById(R.id.cp_useColor_switch); 54     ToggleButton toggleButton = (ToggleButton) findViewById(R.id.cm_toggle_button); 55     Button restore_settings_button = (Button) findViewById(R.id.cm_restore_settings_button); 56  57     restoreLatestSettings(); 58  59     toggleButton.setOnClickListener(new View.OnClickListener() { 60  61       @Override 62       public void onClick(View view) { 63         if (((ToggleButton) view).isChecked()) 64           collectCurrentDarkerSettings(); 65         else 66           ScreenFilterService.removeScreenFilter(); 67       } 68     }); 69  70     restore_settings_button.setOnClickListener(new View.OnClickListener() { 71  72       @Override 73       public void onClick(View view) { 74         DarkerSettings darkerSettings_default = DarkerSettings.getDefaultSettings(); 75         circleSeekBar_brightness.setCurProcess((int) (darkerSettings_default.getBrightness() * 100)); 76         circleSeekBar_alpha.setCurProcess((int) (darkerSettings_default.getAlpha() * 100)); 77         if (aSwitch.isChecked() != darkerSettings_default.isUseColor()) { 78           aSwitch.setChecked(darkerSettings_default.isUseColor()); 79           AlphaAnimation alphaAnimation_1 = new AlphaAnimation(1, 0); 80           alphaAnimation_1.setDuration(300); 81           colorSeekBar.startAnimation(alphaAnimation_1); 82           colorSeekBar.setVisibility(View.INVISIBLE); 83         } 84       } 85     }); 86  87     aSwitch.setOnClickListener(new View.OnClickListener() { 88       @Override 89       public void onClick(View view) { 90         if (((Switch) view).isChecked()) { 91           AlphaAnimation alphaAnimation_0 = new AlphaAnimation(0, 1); 92           alphaAnimation_0.setDuration(300); 93           colorSeekBar.startAnimation(alphaAnimation_0); 94           colorSeekBar.setVisibility(View.VISIBLE); 95         } 96         else { 97           AlphaAnimation alphaAnimation_1 = new AlphaAnimation(1, 0); 98           alphaAnimation_1.setDuration(300); 99           colorSeekBar.startAnimation(alphaAnimation_1);100           colorSeekBar.setVisibility(View.INVISIBLE);101         }102       }103     });104 105   }106 107   @Override108   public boolean onCreateOptionsMenu(Menu menu) {109     // Inflate the menu; this adds items to the action bar if it is present.110     getMenuInflater().inflate(R.menu.menu_main, menu);111     return true;112   }113 114   @Override115   public boolean onOptionsItemSelected(MenuItem item) {116     // Handle action bar item clicks here. The action bar will117     // automatically handle clicks on the Home/Up button, so long118     // as you specify a parent activity in AndroidManifest.119     int id = item.getItemId();120 121     //noinspection SimplifiableIfStatement122     if (id == R.id.action_settings) {123       startActivity(new Intent(Intent.ACTION_VIEW,124           Uri.parse("https://github.com/hwding/make-it-darker")));125     }126 127     if (id == R.id.action_licenses) {128       startActivity(new Intent(MainActivity.this, LicenseActivity.class));129     }130 131     return super.onOptionsItemSelected(item);132   }133 134   private void collectCurrentDarkerSettings() {135     currentDarkerSettings.setBrightness(((float) circleSeekBar_brightness.getCurProcess()) / 100);136     currentDarkerSettings.setAlpha(((float) circleSeekBar_alpha.getCurProcess()) / 100);137     currentDarkerSettings.setUseColor(aSwitch.isChecked());138     currentDarkerSettings.setColor(colorSeekBar.getColor());139     currentDarkerSettings.saveCurrentSettings();140     ScreenFilterService.updateScreenFilter(currentDarkerSettings);141   }142 143   private void restoreLatestSettings() {144     DarkerSettings latestDarkerSettings = DarkerSettings.getCurrentSettings();145     circleSeekBar_alpha.setCurProcess((int) (latestDarkerSettings.getAlpha() * 100));146     circleSeekBar_brightness.setCurProcess((int) (latestDarkerSettings.getBrightness() * 100));147     if (latestDarkerSettings.isUseColor()) {148       aSwitch.setChecked(true);149       colorSeekBar.setVisibility(View.VISIBLE);150     }151     colorSeekBar.setColorBarValue(latestDarkerSettings.getColor());152   }153 154   private void checkPermissions() {155     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {156       if (!Settings.canDrawOverlays(getApplicationContext())) {157         Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);158         intent.setData(Uri.parse("package:" + getPackageName()));159         startActivityForResult(intent, 0);160       }161       else162         prepareForService();163     }164     else165       prepareForService();166   }167 168   @Override169   protected void onActivityResult(int requestCode, int resultCode, Intent data) {170     super.onActivityResult(requestCode, resultCode, data);171     if (requestCode == 0) {172       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {173         if (Settings.canDrawOverlays(this))174           prepareForService();175         else {176           Toast.makeText(getApplicationContext(), "权限请求被拒绝 无法正常工作 :(", Toast.LENGTH_LONG).show();177           finish();178         }179       }180     }181   }182 183   private void prepareForService() {184     intent = new Intent(getApplicationContext(), ScreenFilterService.class);185     startService(intent);186   }187 }

MainActivity.java

高优先级的悬浮窗是敏感权限,6.0以上系统需要特殊处理。

在启动时,首先调用checkPermissions()方法,判断系统版本为6.0及以上时,通过包装一个intent的方式,使app专门请求用户授权此项权限。

返回后通过接收result,再一次判断是否具有drawOverlays的权限,如果没有,则报错并正常退出,如果获取到权限则启动service。

而如果系统版本低于6.0,则可以直接唤起service使系统自动向用户请求该项权限。

请见文章:

http://pcedu.pconline.com.cn/692/6928996.html

http://blog.csdn.net/yangqingqo/article/details/48371123/

http://www.cnblogs.com/mengdd/p/3824782.html

 

另外需要在manifest中声明权限  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 

 

尤其要注意在startService后不能立即读写ScreenFilterService类中的成员变量,否则会引null异常。

所以应首先同app一起自动启动service,并在ScreenFilterService的onCreate方法中使用removeScreenFilter()方法防止filter自动生效。

 

还需注意应在manifest中将mainActivity的启动模式设为单例模式以防止在childActivity中通过navigationIcon返回主activity后导致的重启使filter消失。

 

:) 暑假好开心,月底就要去上海玩了。