目录
2.2 Schedule Dropdown preference
2.5 Color Temperature seekbar preference
2.6 Night display activation preference
1.isNightDisplayAvailable / getMinimumColorTemperature / getMaximumColorTemperature
2.isNightDisplayActivated / setNightDisplayActivated
4.setNightDisplayColorTemperature
5.开机Night Light效果加载以及Settings数据变化监听
写作目的
本文从Android 系统架构角度分层讲述Android10 Night Light实现细节,由表及里,层层深入,尽可能展示出Night Light功能全貌。
定义
- Java类文件:本文中一个java类文件一般用以java为后缀名的java文件表示,本文如无特殊说明 ,java类和java文件表示含义相同
读者
一 Applications层部分
Night Light的Applications层部分,具体是在Settings app里面实现的,从Night Light菜单入口开始讲。
1.Night Light入口菜单
在display_settings.xml添加一个switch类型preference fragement如下:
<com.android.settings.display.NightDisplayPreference
android:key="night_display"
android:title="@string/night_display_title"
android:fragment="com.android.settings.display.NightDisplaySettings"
android:widgetLayout="@null"
settings:widgetLayout="@null"
settings:searchable="false" />
com.android.settings.display.NightDisplayPreference 是对应的后台控制类NightDisplayPreference.java就是一个switch类型preference control,它继承自类SwitchPreference.java,重点负责显示一个Night Light开关状态的summary ,显示summary的方法:
private void updateSummary() {
setSummary(mTimeFormatter.getAutoModeTimeSummary(getContext(), mColorDisplayManager));
}
这个方法调用祖宗类Preference.java的setSummary方法,传入的参数是通过类NightDispalyTimeFormatter.java的getAutoModeTimeSummary方法格式化好的状态字符串,这些字符串要显示在界面上,需要在fragement生命周期里面onAttach方法里面调用updateSummary方法,这个onAttach方法的作用是在Fragment 和 Activity 建立关联调用,也就是Activity 向Fragment 传递数据的时候使用。
另外Night Light还可以在设定的时间段内自动开启关闭,这就需要考虑到用户在设定时间内开启后这个菜单的summary也能实时更新,为了实现这个功能,引入了一个Listener类,被命名为NightDisplayListener.java,它里面引入了ContentObserver机制实现对Night Light的状态(开关、时间设置类型、开始时间、结束时间)进行实时监听,具体是通过在NightDisplayListener.java里面定义一个Callback接口,里面有一系列方法定义(onActivated, onAutoModeChanged, onCustomStartTimeChanged, onCustomEndTimeChanged),然后在NightDisplayPreference.java 里override他们,在这些方法里面再调用setSummary方法
以上部分UML类图如下:
2.Night Light主界面
当我们在Settings->Dispaly窗口点击这个Night Light菜单,就会打开一个fragment,这个fragment的作用就是展示Night Light的主界面,上面的xml里面是这样配置的:android:fragment="com.android.settings.display.NightDisplaySettings"
这个NightDisplaySettings.java就是一个fragment, 这个类主要负责加载主界面的layout以及实现TimePickerDialog的功能,这个设定好时间后,等时间到了就可以自动开启关闭夜间模式,例如将我们在下拉框里面选择Turns on at custom time项,那么夜间模式会在当天22:00自动开启第二天06:00自动关闭,同时也可以点击下面的按钮马上打开Night Light直到第二天06:00自动关闭,再次点击就马上关闭Night Light直到当天22:00打开这个功能,按钮的text会进行提示,Turns on from sunset to sunrise也是这样,这些preference状态的更新同样是用NightDisplayListener来实现,和入口菜单那一节讲的一样,也可以设定其他任意时间段,自定义时间开启UI如下图:
加载主界面的layout核心代码:
@Override
protected int getPreferenceScreenResId() {return R.xml.night_display_settings;}
@Override
public int getHelpResource() {return R.string.help_url_night_display;}
实现TimePickerDialog功能的核心代码
@Override
public Dialog onCreateDialog(final int dialogId) {
if (dialogId == DIALOG_START_TIME || dialogId == DIALOG_END_TIME) {
final LocalTime initialTime;
if (dialogId == DIALOG_START_TIME) {
initialTime = mColorDisplayManager.getNightDisplayCustomStartTime();
} else {
initialTime = mColorDisplayManager.getNightDisplayCustomEndTime();
}
final Context context = getContext();
final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(context);
return new TimePickerDialog(context, (view, hourOfDay, minute) -> {
final LocalTime time = LocalTime.of(hourOfDay, minute);
if (dialogId == DIALOG_START_TIME) {
mColorDisplayManager.setNightDisplayCustomStartTime(time);
} else {
mColorDisplayManager.setNightDisplayCustomEndTime(time);
}
}, initialTime.getHour(), initialTime.getMinute(), use24HourFormat);
}
return super.onCreateDialog(dialogId);
}
更新preference状态
@Override
public void onAutoModeChanged(int autoMode) {
// Update auto mode, start time, and end time preferences.
updatePreferenceStates();
}
@Override
public void onColorTemperatureChanged(int colorTemperature) {
// Update temperature preference.
updatePreferenceStates();
}
@Override
public void onCustomStartTimeChanged(LocalTime startTime) {
// Update start time preference.
updatePreferenceStates();
}
@Override
public void onCustomEndTimeChanged(LocalTime endTime) {
// Update end time preference.
updatePreferenceStates();
}
以上部分UML类图如下:
2.1 Night Light主界面布局
我们先来看看布局xml文件
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/night_display_title"
android:key="night_display_title"
settings:keywords="@string/keywords_display_night_display">
<DropDownPreference
android:key="night_display_auto_mode"
android:title="@string/night_display_auto_mode_title"
android:summary="%s"
settings:controller="com.android.settings.display.NightDisplayAutoModePreferenceController" />
<Preference
android:key="night_display_start_time"
android:title="@string/night_display_start_time_title"
settings:controller="com.android.settings.display.NightDisplayCustomStartTimePreferenceController" />
<Preference
android:key="night_display_end_time"
android:title="@string/night_display_end_time_title"
settings:controller="com.android.settings.display.NightDisplayCustomEndTimePreferenceController" />
<com.android.settings.widget.SeekBarPreference
android:key="night_display_temperature"
android:title="@string/night_display_temperature_title"
settings:keywords="@string/keywords_display_night_display"
settings:controller="com.android.settings.display.NightDisplayIntensityPreferenceController"
settings:unavailableSliceSubtitle="@string/night_display_not_currently_on" />
<com.android.settingslib.widget.LayoutPreference
android:key="night_display_activated"
android:title="@string/night_display_title"
android:selectable="false"
android:layout="@layout/night_display_activation_button"
settings:keywords="@string/keywords_display_night_display"
settings:controller="com.android.settings.display.NightDisplayActivationPreferenceController" />
<PreferenceCategory android:key="night_display_footer_category">
<com.android.settingslib.widget.FooterPreference />
</PreferenceCategory>
</PreferenceScreen>
这个布局是在PreferenceScreen用了6个Preference,每个preference的后台处理逻辑是在标签settings:controller指定的类里面,android:title用来指定preference在界面上显示的标题, 标注在主界面上看比较直观:
下面逐个分析每个preference的实现,
2.2 Schedule Dropdown preference
这个下拉框的作用是设置NightLight时间显示模式,有三种模式:
None(手动开关显示),
Truns on at custom time(自定义时间显示),
Turns on from sunset to sunrise(夜间显示)
选择不用的模式 ,start_time preference,end_time preference和activation_button toggle preference也会跟着刷新,下面具体看下后台控制类
NightDisplayAutoModePreferenceController.java的实现:
下拉框加载3种模式选项
@override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
mPreference.setEntries(new CharSequence[]{
mContext.getString(R.string.night_display_auto_mode_never),
mContext.getString(R.string.night_display_auto_mode_custom),
mContext.getString(R.string.night_display_auto_mode_twilight)
});
mPreference.setEntryValues(new CharSequence[]{
String.valueOf(ColorDisplayManager.AUTO_MODE_DISABLED),
String.valueOf(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME),
String.valueOf(ColorDisplayManager.AUTO_MODE_TWILIGHT)
});
}
通过ColorDisplayManager类初始化设置的模式
@Override
public final void updateState(Preference preference) {
mPreference.setValue(String.valueOf(mColorDisplayManager.getNightDisplayAutoMode()));
}
下拉框选中某一项,触发onPreferenceChange事件,后台通过ColorDisplayManager类设置当前选择的模式
@Override
public final boolean onPreferenceChange(Preference preference, Object newValue) {
return mColorDisplayManager.setNightDisplayAutoMode(Integer.parseInt((String) newValue));
}
以上部分UML类图如下:
2.3 Start time preference
这个preference会随着时间显示模式的改变而刷新自身状态(是否显示以及显示开始时间),刷新状态是通过重载AbstractPreferenceController的updateState方法来实现
@Override
public final void updateState(Preference preference) {
// 是否显示
preference.setVisible(mColorDisplayManager.getNightDisplayAutoMode()
== ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
//显示开始时间
preference.setSummary(mTimeFormatter.getFormattedTimeString(
mColorDisplayManager.getNightDisplayCustomStartTime()));
}
另外点击这个preference弹出TimePickerDialog的实现细节在Night Light主界面一节已经讲过,这里不再赘述。
以上部分UML类图如下:
2.4 End time preference
这个preference的作用和Start time preference一样,会随着时间显示模式的改变而刷新自身状态(是否显示以及显示结束时间),刷新状态是通过重载AbstractPreferenceController的updateState方法来实现
@Override
public final void updateState(Preference preference) {
//是否显示
preference.setVisible(mColorDisplayManager.getNightDisplayAutoMode()
== ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
//显示结束时间
preference.setSummary(mTimeFormatter.getFormattedTimeString(
mColorDisplayManager.getNightDisplayCustomEndTime()));
}
另外点击这个preference弹出TimePickerDialog的实现细节在Night Light主界面一节已经讲过,这里不再赘述。
以上部分UML类图如下:
2.5 Color Temperature seekbar preference
这个seekbar 类型的preference实现Night Light的核心功能:
加载设置夜间模式的屏幕效果,通过滑动条的调节可以实时显示夜间模式的屏幕效果,这个屏幕效果具体讲就是改变屏幕的色温值,滑动条的最左端和最右端分别对应屏幕最大色温值和最小色温值,可以在这个区间通过滑动条进行色温调整。后台控制类是NightDisplayIntensityPreferenceController.java他继承了SliderPreferenceController类
下面看下里面主要的方法实现:
声明一个ColorDisplayManager类的实例mColorDisplayManager,这个ColorDisplayManager.java的实现位于framework里面,顾名思义是对屏幕的颜色管理的类,主要是定义了屏幕效果的API,包括夜间模式、白平衡等。
private ColorDisplayManager mColorDisplayManager;
NightDisplayIntensityPreferenceController类的构造函数,在这个里面可以做一些变量的初始化工作,比如在这里初始化了mColorDisplayManager。
public NightDisplayIntensityPreferenceController(Context context, String key) {
super(context, key);
mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
}
获取seekbar的可用状态,这里首先判断ColorDisplayManager.isNightDisplayAvailable的结果,它读取framework config.xnl配置的值,这个配置设备是否带 Night Light功能,后面接着判断mColorDisplayManager.isNightDisplayActivated的结果,它返回的是Night Light功能是否打开,在主界面上我们可以点击滑动条下方的按钮打开关闭Night Light,下一小节会讲到这个。
@Override
public int getAvailabilityStatus() {
if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
return UNSUPPORTED_ON_DEVICE;
} else if (!mColorDisplayManager.isNightDisplayActivated()) {
return DISABLED_DEPENDENT_SETTING;
}
return AVAILABLE;
}
重载isSliceable,判断当前preference的key是否是xml布局里面设置的night_display_temperature。/
//if the controller should be used externally as a Slice.
@Override
public boolean isSliceable() {
return TextUtils.equals(getPreferenceKey(), "night_display_temperature");
}
设置seekbar的最大值/最小值,由最大色温/最小色温换算而来。
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final SeekBarPreference preference = screen.findPreference(getPreferenceKey());
preference.setContinuousUpdates(true);
preference.setMax(getMax());
preference.setMin(getMin());
}
读取mColorDisplayManager.isNightDisplayActivated()的值(它返回的是Night Light功能是否打开)来决定seekbar是否可用,窗口的刷新会触发preference的updateState事件
@Override
public final void updateState(Preference preference) {
super.updateState(preference);
preference.setEnabled(mColorDisplayManager.isNightDisplayActivated());
}
设置滑动条的值,通过读取当前色温换算成滑动条的值。
@Override
public int getSliderPosition() {
return convertTemperature(mColorDisplayManager.getNightDisplayColorTemperature());
}
滑动条滑动过程中,将滑动条的值转换成色温值,通过这个色温值参数设置屏幕当前的夜间模式效果,setNightDisplayColorTemperature是Night Light功能的核心API,他通过将色温作为参数通过算法去改变屏幕显示的RGB色彩比例,进而达到调整屏幕显示效果的目的,后面framework章节重点讲述。
@Override
public boolean setSliderPosition(int position) {
return
mColorDisplayManager.setNightDisplayColorTemperature(convertTemperature(position));
}
通过ColorDisplayManager的静态方法getMinimumColorTemperature读取framework config.xnl配置的最小色温值,然后色温转换方法convertTemperature将色温转换成seekbar最右端的值(最大值)。在displayPreference方法里面调用它设置seekbar的最右端的值(最大值):
preference.setMax(getMax());
@Override
public int getMax() {
return convertTemperature(ColorDisplayManager.getMinimumColorTemperature(mContext));
}
直接设置seekbar的最左端的值(最小值)为0。
@Override
public int getMin() {
// The min should always be 0 in this case.
return 0;
}
色温转换方法convertTemperature将色温转换成seekbar的值
算法:
根据输入的色温值,将经过反转和范围调整的值转换为SeekBar的原始值(即[0,maxTemp-minTemp])。
最大色温值-当前色温值
private int convertTemperature(int temperature) {
return ColorDisplayManager.getMaximumColorTemperature(mContext) - temperature;
}
以上部分UML类图如下:
2.6 Night display activation preference
这个preference是一个按钮,点击它可以打开或关闭Night Light功能,后台控制类为NightDisplayActivationPreferenceController.java,继承了TogglePreferenceController. Java这个切换按钮的类,下面来看具体的实现逻辑:
//引用类声明变量
private ColorDisplayManager mColorDisplayManager;
private NightDisplayTimeFormatter mTimeFormatter;
private Button mTurnOffButton;
private Button mTurnOnButton;
//在一个监听里面重载开关按钮是Click事件
private final OnClickListener mListener = new OnClickListener() {
@Override
public void onClick(View v) {
//开关Night Light功能 mColorDisplayManager.setNightDisplayActivated(!mColorDisplayManager.isNightDis playActivated());
//根据时间显示模式更新按钮的text属性,根据Night Light是否打开更新mTurnOffButton和mTurnOnButton的可见性
updateStateInternal();
}
};
//NightDisplayActivationPreferenceController类构造函数,初始化mColorDisplayManager和mTimeFormatter
public NightDisplayActivationPreferenceController(Context context, String key) {
super(context, key);
mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
mTimeFormatter = new NightDisplayTimeFormatter(context);
}
//获取按钮的可用状态,这里首先判断ColorDisplayManager.isNightDisplayAvailable的结果,它读取framework config.xnl配置的值,这个配置设备是否带 Night Light功能
@Override
public int getAvailabilityStatus() {
return ColorDisplayManager.isNightDisplayAvailable(mContext) ? AVAILABLE: UNSUPPORTED_ON_DEVICE;
}
//重载isSliceable,判断当前preference的key是否是xml布局里面设置的night_display_activated。
@Override
public boolean isSliceable() {
return TextUtils.equals(getPreferenceKey(), "night_display_activated");
}
//在preference的display事件里面初始化mTurnOnButton和mTurnOffButton以及设置相应的click事件
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final LayoutPreference preference = screen.findPreference(getPreferenceKey());
mTurnOnButton = preference.findViewById(R.id.night_display_turn_on_button);
mTurnOnButton.setOnClickListener(mListener);
mTurnOffButton = preference.findViewById(R.id.night_display_turn_off_button);
mTurnOffButton.setOnClickListener(mListener);
}
//在preference的updateState事件里面,根据时间显示模式更新按钮的text属性,根据Night Light是否打开更新mTurnOffButton和mTurnOnButton的可见性
@Override
public final void updateState(Preference preference) {
updateStateInternal();
}
//根据Night Light是否打开来初始化按钮的可检查性
@Override
public boolean isChecked() {
return mColorDisplayManager.isNightDisplayActivated();
}
//根据按钮的可检查性设置Night Light功能开关
@Override
public boolean setChecked(boolean isChecked) {
return mColorDisplayManager.setNightDisplayActivated(isChecked);
}
//根据时间显示模式类别更新按钮的摘要
@Override
public CharSequence getSummary() {
return mTimeFormatter.getAutoModeSummary(mContext, mColorDisplayManager);
}
// updateStateInternal方法具体实现:根据时间显示模式更新按钮的text属性、根据Night Light是否打开更新mTurnOffButton和mTurnOnButton的可见性
private void updateStateInternal() {
if (mTurnOnButton == null || mTurnOffButton == null) {
return;
}
final boolean isActivated =mColorDisplayManager.isNightDisplayActivated();
final int autoMode = mColorDisplayManager.getNightDisplayAutoMode();
String buttonText;
//根据时间显示模式更新按钮的text属性
if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) {
buttonText = mContext.getString(isActivated
? R.string.night_display_activation_off_custom
: R.string.night_display_activation_on_custom,
mTimeFormatter.getFormattedTimeString(isActivated
? mColorDisplayManager.getNightDisplayCustomStartTime()
: mColorDisplayManager.getNightDisplayCustomEndTime()));
} else if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
buttonText = mContext.getString(isActivated
? R.string.night_display_activation_off_twilight
: R.string.night_display_activation_on_twilight);
} else {
buttonText = mContext.getString(isActivated
? R.string.night_display_activation_off_manual
: R.string.night_display_activation_on_manual);
}
//根据Night Light是否打开更新mTurnOffButton和mTurnOnButton的可见性
if (isActivated) {
mTurnOnButton.setVisibility(View.GONE);
mTurnOffButton.setVisibility(View.VISIBLE);
mTurnOffButton.setText(buttonText);
} else {
mTurnOnButton.setVisibility(View.VISIBLE);
mTurnOffButton.setVisibility(View.GONE);
mTurnOnButton.setText(buttonText);
}
}
以上部分UML类图如下:
2.7 Night display footer categor
这个footer category显示在主界面底部,相当于一个Help,用来对Night Light功能进行解释说明,控件类型为preference category,后台控制类为NightDisplayFooterPreferenceController.java,这个控制类的加载不是在xml配置,而是在NightDisplaySettings.java里面重载createPreferenceControllers方法,这个方法里面调用自定义buildPreferenceControllers方法
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>(1);
controllers.add(new NightDisplayFooterPreferenceController(context));
return controllers;
}
验证了下,通过在xml里面配置也是可以加载到这个类的,如下:
<PreferenceCategory android:key="night_display_footer_category" settings:controller="com.android.settings.display.NightDisplayFooterPreferenceController">
<com.android.settingslib.widget.FooterPreference />
</PreferenceCategory>
NightDisplayFooterPreferenceController.java的实现代码,它继承自BasePreferenceController类:
//构造函数
public NightDisplayFooterPreferenceController(Context context) {
super(context, FooterPreference.KEY_FOOTER);
}
//重载getAvailabilityStatus方法
@Override
public int getAvailabilityStatus() {
return ColorDisplayManager.isNightDisplayAvailable(mContext) ? AVAILABLE
: UNSUPPORTED_ON_DEVICE;
}
//重载updateState方法, 通过preference setTitle加载帮助说明字符串
@Override
public void updateState(Preference preference) {
preference.setTitle(R.string.night_display_text);
}
3.Night Light的ConditionalCard
Night Light被打开时,在Settings顶级菜单上方ConditionalCard显示区域会显示夜间模式的ConditionalCard,如下图:
ConditionalCard是一个已经封装好的control,界面布局在以下布局xml里面实现:
packages/apps/Settings/res/layout/conditional_card_footer.xml
packages/apps/Settings/res/layout/conditional_card_full_tile.xml
packages/apps/Settings/res/layout/conditional_card_half_tilexml
packages/apps/Settings/res/layout/conditional_card_header.xml
packages/apps/Settings/res/layout/conditional_card_header_icon.xml
这里面核心是conditional_card_full_tile.xml、conditional_card_half_tilexml
里面封装了com.google.android.material.card.MaterialCardView类,这个类无源码。
我们要实现Night Light的ConditionalCard,只需要声明一个继承ConditionalCardController的类然后在类里面实现ConditionalCardController接口的方法就可以,这个类就是后台控制类NightDisplayConditionController.java,我们重点看下这个NightDisplayConditionController.java
//声明变量
static final int ID = Objects.hash("NightDisplayConditionController");
private final Context mAppContext;
private final ConditionManager mConditionManager;
private final ColorDisplayManager mColorDisplayManager;
private final NightDisplayListener mNightDisplayListener;
//构造函数
public NightDisplayConditionController(Context appContext, ConditionManager manager) {
mAppContext = appContext;
mConditionManager = manager;
mColorDisplayManager = appContext.getSystemService(ColorDisplayManager.class);
mNightDisplayListener = new NightDisplayListener(appContext);
}
//实现ConditionalCardController接口的getId方法 返回类名
@Override
public long getId() {
return ID;
}
//实现ConditionalCardController接口的isDisplayable方法, 返回Night Light ConditionalCard是否可见,夜间模式打开就可见,反之隐藏
@Override
public boolean isDisplayable() {
return mColorDisplayManager.isNightDisplayActivated();
}
//实现ConditionalCardController接口的onPrimaryClick方法,点击ConditionalCard进入Night Light主界面
@Override
public void onPrimaryClick(Context context) {
new SubSettingLauncher(context)
.setDestination(NightDisplaySettings.class.getName())
.setSourceMetricsCategory(SettingsEnums.SETTINGS_HOMEPAGE)
.setTitleRes(R.string.night_display_title)
.launch();
}
//实现ConditionalCardController接口的onActionClick方法,点击下部的Turn off按钮,关掉Night Light功能
@Override
public void onActionClick() {
mColorDisplayManager.setNightDisplayActivated(false);
}
//实现ConditionalCardController接口的buildContextualCard方法,绘制Night Light ConditionalCard界面
@Override
public ContextualCard buildContextualCard() {
return new ConditionalContextualCard.Builder()
.setConditionId(ID)
.setMetricsConstant(SettingsEnums.SETTINGS_CONDITION_NIGHT_DISPLAY)
.setActionText(mAppContext.getText(R.string.condition_turn_off))
.setName(mAppContext.getPackageName() + "/"
+ mAppContext.getText(R.string.condition_night_display_title))
.setTitleText(mAppContext.getText(
R.string.condition_night_display_title).toString())
.setSummaryText(
mAppContext.getText(R.string.condition_night_display_summary).toString())
.setIconDrawable(mAppContext.getDrawable(R.drawable.ic_settings_night_display))
.setViewType(ConditionContextualCardRenderer.VIEW_TYPE_HALF_WIDTH)
.build();
}
//实现ConditionalCardController接口的startMonitoringStateChange方法,设置对Night Light开关、时间设置类型等状态的监听
@Override
public void startMonitoringStateChange() {
mNightDisplayListener.setCallback(this);
}
//实现ConditionalCardController接口的的stopMonitoringStateChange方法,停止对Night Light开关、时间设置类型等状态的监听
@Override
public void stopMonitoringStateChange() {
mNightDisplayListener.setCallback(null);
}
//实现NightDisplayListener.Callback接口的onActivated方法,点击下部的Turn off按钮,是Night Light ConditionalCard界面消失
@Override
public void onActivated(boolean activated) {
mConditionManager.onConditionChanged();
}
以上部分UML类图如下:
二 Framework层部分
通过app层的分析我们可以看到Night Light功能的实现需要调用到framework层的ColorDisplayManager类的下列方法:
isNightDisplayAvailable(Context context) :boolean
isNightDisplayActivated():boolean
setNightDisplayActivated(boolean activated):boolean
getNightDisplayAutoMode():int
setNightDisplayAutoMode(int automode):boolean
getNightDisplayCustomStartTime:LocalTime
getNightDisplayCustomEndTime:LocalTime
setNightDisplayCustomStartTime(LocalTime startTime):boolean
setNightDisplayCustomEndTime(LocalTime endTime):boolean
getMinimumColorTemperature(Context context):int
getMaximumColorTemperature(Context context):int
setNightDisplayColorTemperature(int temperature):boolean
它在初始化它的时候是通过这种方式,
appContext.getSystemService(ColorDisplayManager.class);
从这里可以看出ColorDisplayManager其实是一个SystemService,具体点讲就是一个color display service ,关于像ColorDisplayService这种SystemService实现细节,需要了解的可以看下这篇文章:
这里不做重点讲述。
我们讲Night Light的Framework层部分紧紧围绕实现Night Light功能的接口来阐述,这些接口按照实现方式分类讲解
1.isNightDisplayAvailable / getMinimumColorTemperature / getMaximumColorTemperature
这3个方法实现比较简单,最终实现都是在
frameworks/base/core/java/android/hardware/display/ColorDisplayManager.java里面
//isNightDisplayAvailable的方法实现
public static boolean isNightDisplayAvailable(Context context) {
return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable);
}
//getMinimumColorTemperature方法实现
public static int getMinimumColorTemperature(Context context) {
return context.getResources()
.getInteger(R.integer.config_nightDisplayColorTemperatureMin);
}
// getMaximumColorTemperature的方法实现
public static int getMaximumColorTemperature(Context context) {
return context.getResources()
.getInteger(R.integer.config_nightDisplayColorTemperatureMax);
}
可以看到这3个方法的共同特点都是都是获取资源配置xml文件的值,
config_nightDisplayAvailable
config_nightDisplayColorTemperatureMin
config_nightDisplayColorTemperatureMax
这个资源配置xml文件为
frameworks/base/core/res/res/values/config.xml
具体的配置定义为
<!-- Control whether Night display is available. This should only be enabled on devices
that have a HWC implementation that can apply the matrix passed to setColorTransform
without impacting power, performance, and app compatibility (e.g. protected content). -->
<bool name="config_nightDisplayAvailable">
@bool/config_setColorTransformAccelerated
</bool>
<!-- Minimum color temperature, in Kelvin, supported by Night display. -->
<integer name="config_nightDisplayColorTemperatureMin">2596</integer>
<!-- Maximum color temperature, in Kelvin, supported by Night display. -->
<integer name="config_nightDisplayColorTemperatureMax">3158</integer>
这3个值是系统编译成软件之前已经配置好的,只读的无法修改。
2.isNightDisplayActivated / setNightDisplayActivated
先看isNightDisplayActivated,最终实现在
frameworks/base/services/core/java/com/android/server/display/color/TintController.java里面
public boolean isActivated() {
return mIsActivated != null && mIsActivated;
}
变量mIsActivated的值会在接下来要讲的setNightDisplayActivated
方法中进行赋值操作,然后经过一层层调用,最后封装在frameworks/base/core/java/android/hardware/display/ColorDisplayManager.java的isNightDisplayActivated方法中供app使用
public boolean isNightDisplayActivated() {
return mManager.isNightDisplayActivated();
}
整个调用时序图如下:
下面详细看下setNightDisplayActivated的实现:
setNightDisplayActivated调用流程和isNightDisplayActivated一样,可以参考isNightDisplayActivated。
它的核心实现是调用ColorDisplayService.内部的私有final类NightDisplayTintController里面的setActivated方法
public void setActivated(Boolean activated, @NonNull LocalDateTime lastActivationTime){
//当第一开机或机器恢复出厂时,参数activated缺省值为null,此时直接执行NightDisplayTintController的父类TintController的setActivated方法设置mIsActivated变量为null,然后直接return
if (activated == null) {
super.setActivated(null);
return;
}
//用activated!= isActivated();判断Night Light的activated是否已经发生变化
boolean activationStateChanged = activated != isActivated();
// TintController类里面isActivatedStateNotSet方法判断activated是否为null
这里是判断如果不是第一开机或恢复出厂并且activated已经发生变化,则把最后一次设置时间的值(直接读取当前系统时间,见上一级调用)保存到ContentProvider里面,这个NIGHT_DISPLAY_LAST_ACTIVATED_TIME在时间段内开启/关闭Night Light实现代码里面多处用到。
if (!isActivatedStateNotSet() && activationStateChanged) {
// This is a true state change, so set this as the last activation time.
Secure.putStringForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
lastActivationTime.toString(),
mCurrentUser);
}
//这里是判断如果是第一开机或恢复出厂或者activated已经发生变化,执行TintController类里面setActivated方法给mIsActivated赋值
if (isActivatedStateNotSet() || activationStateChanged) {
super.setActivated(activated);
//然后通过调用isActivatedSetting 读取ContentProvider里面存储的NIGHT_DISPLAY_ACTIVATED活动状态的值判断是否和activated的值相等,如果不相等就需要把更新activated 值更新ContentProvider里面存储的NIGHT_DISPLAY_ACTIVATED值
if (isActivatedSetting() != activated) {
Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_ACTIVATED,
activated ? 1 : 0, mCurrentUser);
}
//然后执行onActivated方法,后面详细讲
onActivated(activated);
}
}
// isActivatedSetting 方法实现,读取ContentProvider里面存储的NIGHT_DISPLAY_ACTIVATED活动状态的值
boolean isActivatedSetting() {
if (mCurrentUser == UserHandle.USER_NULL) {
return false;
}
return Secure.getIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_ACTIVATED, 0, mCurrentUser) == 1;
}
//onActivated方法实现
private void onActivated(boolean activated) {
Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
//判断是否设置了自动模式(在设置好的时间段内自动开启/关闭Night Light),如果开启了自动模式则设置自动模式的activated状态
if (mNightDisplayAutoMode != null) {
mNightDisplayAutoMode.onActivated(activated);
}
//判断另一种白平衡屏幕效果是否开启,如果开启则要调用updateDisplayWhiteBalanceStatus方法和 Night Light做互斥(Night Light和white balance 这2种屏幕效果有冲突,不能同时打开),关于updateDisplayWhiteBalanceStatus的实现这里不做详细分析
if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
updateDisplayWhiteBalanceStatus();
}
//发送MSG_APPLY_NIGHT_DISPLAY_ANIMATED在mHandler里面执行
应用当前色温矩阵的方法applyTint设置屏幕夜间模式效果,这个方法将在后面设置色温矩阵的章节详细分析
mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_ANIMATED);
}
setNightDisplayActivated执行流程图如下:
3.getNightDisplayAutoMode/setNightDisplayAutoMode / getNightDisplayCustomStartTime/setNightDisplayCustomStartTime / getNightDisplayCustomEndTime/ setNightDisplayCustomEndTime
这几个方法都是从ContentProvider里面存取数据,因此放到一个章节讲
先看getNightDisplayAutoMode/setNightDisplayAutoMode的核心实现
他们是定义在ColorDisplayService类里面的私有方法
private int getNightDisplayAutoModeInternal() {
//调用getNightDisplayAutoModeRawInternal读取存于ContentProvider的NIGHT_DISPLAY_AUTO_MODE值
int autoMode = getNightDisplayAutoModeRawInternal();
//如果autoMode没有被设置过,读取config.xml资源里面自动模式的默认值
if (autoMode == NOT_SET) {
autoMode = getContext().getResources().getInteger(
R.integer.config_defaultNightDisplayAutoMode);
}
//对autoMode值的范围进行检查
if (autoMode != AUTO_MODE_DISABLED
&& autoMode != AUTO_MODE_CUSTOM_TIME
&& autoMode != AUTO_MODE_TWILIGHT) {
Slog.e(TAG, "Invalid autoMode: " + autoMode);
autoMode = AUTO_MODE_DISABLED;
}
return autoMode;
}
//getNightDisplayAutoModeRawInternal的实现
private int getNightDisplayAutoModeRawInternal() {
if (mCurrentUser == UserHandle.USER_NULL) {
return NOT_SET;
}
Return Secure.getIntForUser(getContext().getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE,NOT_SET, mCurrentUser);
}
private boolean setNightDisplayAutoModeInternal(@AutoMode int autoMode) {
//更新NIGHT_DISPLAY_LAST_ACTIVATED_TIME
if (getNightDisplayAutoModeInternal() != autoMode) {
Secure.putStringForUser(getContext().getContentResolver(),
Secure. NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
null,
mCurrentUser);
}
//设置NIGHT_DISPLAY_AUTO_MODE
return Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mCurrentUser);
}
下面是getNightDisplayCustomStartTime/setNightDisplayCustomStartTime的实现
他们同样是定义在ColorDisplayService类里面的私有方法
private Time getNightDisplayCustomStartTimeInternal() {
/*先从ContentProvider读取NIGHT_DISPLAY_CUSTOM_START_TIME,
如果读到的是NOT_SET,则从config.xml配置资源读取 config_defaultNightDisplayCustomStartTim默认值*/
int startTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NOT_SET, mCurrentUser);
if (startTimeValue == NOT_SET) {
startTimeValue = getContext().getResources().getInteger(
R.integer.config_defaultNightDisplayCustomStartTime);
}
return new Time(LocalTime.ofSecondOfDay(startTimeValue / 1000));
}
private boolean setNightDisplayCustomStartTimeInternal(Time startTime) {
//保存ContentProvider里面NIGHT_DISPLAY_CUSTOM_START_TIME的值
return Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
startTime.getLocalTime().toSecondOfDay() * 1000,
mCurrentUser);
}
getNightDisplayCustomEndTime/ setNightDisplayCustomEndTime的实现和getNightDisplayCustomStartTime/setNightDisplayCustomStartTime完全一样,这里只贴出代码,不重复解释。
private Time getNightDisplayCustomEndTimeInternal() {
int endTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NOT_SET, mCurrentUser);
if (endTimeValue == NOT_SET) {
endTimeValue = getContext().getResources().getInteger(
R.integer.config_defaultNightDisplayCustomEndTime);
}
return new Time(LocalTime.ofSecondOfDay(endTimeValue / 1000));
}
private boolean setNightDisplayCustomEndTimeInternal(Time endTime) {
return Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.getLocalTime().toSecondOfDay() * 1000,mCurrentUser);
}
4.setNightDisplayColorTemperature
这个方法是Night Light屏幕效果实现的核心,
4.1 调用时序图
从android架构角度看,调用的顺序为:
app --------------------à framework --------------------ànative
除了Settings app在app 层,SurfaceFlinger服务在native层,其他方法的定义实现全部在framework层。
下面详细讲述下framework层的关键方法实现细节
4.2 framework层的关键方法实现和调用流程
从表层位于ColorDisplayService.java的私有final类NightDisplayTintController的setColorTemperature方法开始层层递进讲:
boolean setColorTemperature(int temperature) {
mColorTemp = temperature;
//先把从settings里面滑动条对应的色温值保存到数据库,键值为NIGHT_DISPLAY_COLOR_TEMPERATURE
final boolean success = Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, temperature, mCurrentUser);
onColorTemperatureChanged(temperature);
return success;
}
然后调用NightDisplayTintController的onColorTemperatureChanged方法,这个方法只是个中间过渡
void onColorTemperatureChanged(int temperature) {
setMatrix (temperature);
mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE);
}
先看下setMatrix,这个是重载了TintController的setMatrix方法
@Override
public void setMatrix(int cct) {
if (mMatrix.length != 16) {
Slog.d(TAG, "The display transformation matrix must be 4x4");
return;
}
/*调用OpenGL(Open Graphics Library开发图形接口)的setIdentityM初始化一个单位矩阵
如下图
*/
Matrix.setIdentityM(mMatrix, 0);
//用色温参数和RGB色温系数进行换算分别得到red/ green/ blue值,RGB色温系数是一个数组常量config_nightDisplayColorTemperatureCoefficients存储于config资源里面
final float squareTemperature = cct * cct;
final float red = squareTemperature * mColorTempCoefficients[0]
+ cct * mColorTempCoefficients[1] + mColorTempCoefficients[2];
final float green = squareTemperature * mColorTempCoefficients[3]
+ cct * mColorTempCoefficients[4] + mColorTempCoefficients[5];
final float blue = squareTemperature * mColorTempCoefficients[6]
+ cct * mColorTempCoefficients[7] + mColorTempCoefficients[8];
/*把red/ green/ blue值赋给mMatrix数组第0/5/10 号成员
得到如下矩阵,这个矩阵后面设置的屏幕效果显示逻辑会用到
*/
mMatrix[0] = red;
mMatrix[5] = green;
mMatrix[10] = blue;
}
mHandler发送MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE消息会执行ColorDisplayService类的私有applyTint方法
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE:
applyTint(mNightDisplayTintController, true);
break;
...
}
}
下面看下applyTint方法的实现,它的作用是应用当前色温矩阵mMatrix,或在禁用时将其删除
private void applyTint(TintController tintController, boolean immediate) {
tintController.cancelAnimator();
//返回DisplayTransformManager本地服务实例
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
//返回给定级别的颜色变换矩阵集的副本
final float[] from = dtm.getColorMatrix(tintController.getLevel());
// tintController.getMatrix()返回当前矩阵mMatrix
final float[] to = tintController.getMatrix();
// 参数immediate传进来为true
if (immediate) {
dtm.setColorMatrix(tintController.getLevel(), to);
} else {
tintController.setAnimator(ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
from == null ? MATRIX_IDENTITY : from, to));
tintController.getAnimator().setDuration(TRANSITION_DURATION);
tintController.getAnimator().setInterpolator(AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.fast_out_slow_in));
tintController.getAnimator().addUpdateListener((ValueAnimator animator) -> {
final float[] value = (float[]) animator.getAnimatedValue();
dtm.setColorMatrix(tintController.getLevel(), value);
});
tintController.getAnimator().addListener(new AnimatorListenerAdapter() {
private boolean mIsCancelled;
@Override
public void onAnimationCancel(Animator animator) {
mIsCancelled = true;
}
@Override
public void onAnimationEnd(Animator animator) {
Slog.d(TAG, tintController.getClass().getSimpleName()
+ " Animation cancelled: " + mIsCancelled
+ " to matrix: " + TintController.matrixToString(to, 16));
if (!mIsCancelled) {
/*确保在动画结束时设置最终颜色矩阵。如果
动画被取消,然后不要设置最终的颜色矩阵,这样新的动画师就可以从这个停止的地方开始。*/
dtm.setColorMatrix(tintController.getLevel(), to);
}
tintController.setAnimator(null);
}
});
tintController.getAnimator().start();
}
}
接着看下DisplayTransformManager 服务里面的setColorMatrix方法
/*设置并应用给定级别的当前颜色变换矩阵。
*注意:所有颜色变换都是首先按升序(基于应用到显示之前的级别)组合成一个矩阵
*@param level用于识别和合成颜色变换的级别(低->高)
*@param value 4x4颜色转换矩阵(按列主要顺序),或{@code null}到
*移除与所提供级别关联的颜色变换矩阵*/
public void setColorMatrix(int level, float[] value) {
if (value != null && value.length != 16) {
throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)"
+ ", actual length: " + value.length);
}
synchronized (mColorMatrix) {
final float[] oldValue = mColorMatrix.get(level);
if (!Arrays.equals(oldValue, value)) {
if (value == null) {
mColorMatrix.remove(level);
} else if (oldValue == null) {
//添加从level到指定值Arrays.copyOf(value, value.length)的映射,如果有指定键,则替换指定键的先前映射
mColorMatrix.put(level, Arrays.copyOf(value, value.length));
} else {
System.arraycopy(value, 0, oldValue, 0, value.length);
}
//更新当前颜色变换.
applyColorMatrix(computeColorMatrixLocked());
}
}
}
接着调用颜色变换的方法applyColorMatrix
首先是调用computeColorMatrixLocked方法返回当前所有颜色矩阵的组成,如果没有,则返回null
private float[] computeColorMatrixLocked() {
final int count = mColorMatrix.size();
if (count == 0) {
return null;
}
final float[][] result = mTempColorMatrix;
Matrix.setIdentityM(result[0], 0);
for (int i = 0; i < count; i++) {
float[] rhs = mColorMatrix.valueAt(i);
//将两个4x4矩阵相乘,并将结果存储在第三个4x4矩阵中
Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0);
}
return result[count % 2];
}
private static void applyColorMatrix(float[] m) {
//启动native层SurfaceFlinger服务
final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
if (flinger != null) {
final Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
//将颜色矩阵保存在data里
if (m != null) {
data.writeInt(1);
for (int i = 0; i < 16; i++) {
data.writeFloat(m[i]);
}
} else {
data.writeInt(0);
}
try {
/*调用SurfaceFlinger服务transact方法处理颜色矩阵,SurfaceFlinger是Android的一个native进程,负责将图层进行合成,图层叠加起来就构成了我们看到的界面。通过binder直接设置到SurfaceFlinger,对应的Binder 命令为SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX*/
flinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to set color transform", ex);
} finally {
data.recycle();
}
}
}
这个transact方法实现在native层的SurfaceFlinger.cpp里,传入的参数SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX值为1015
核心方法实现就讲到native层这里
5.开机Night Light效果加载以及Settings数据变化监听
Android系统启动会执行ColorDisplayService.java(继承了SystemService类)的onBootPhase方法
public void onBootPhase(int phase) {
if (phase >= PHASE_BOOT_COMPLETED) {
mBootCompleted = true;
// Register listeners now that boot is complete.
if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
mHandler.sendEmptyMessage(MSG_SET_UP);
}
}
}
mHandler线程发送MSG_SET_UP,然后handleMessage方法里面会调用setUp
private final class TintHandler extends Handler {
private TintHandler(Looper looper) {
super(looper, null, true /* async */);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
…
case MSG_SET_UP:
setUp();
break;
…
}
}
}
下面看下setUp()实现
private void setUp() {
Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
// Listen for external changes to any of the settings.
…
if (mNightDisplayTintController.isAvailable(getContext())) {
// Reset the activated state.
mNightDisplayTintController.setActivated(null);
//准备Night Light色彩变换矩阵
mNightDisplayTintController
.setUp(getContext(),DisplayTransformManager.needsLinearColorMatrix());
mNightDisplayTintController
.setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
// Initialize the current auto mode.
onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal())
// Force the initialization of the current saved activation state.
if (mNightDisplayTintController.isActivatedStateNotSet()) {
mNightDisplayTintController
.setActivated(mNightDisplayTintController.isActivatedSetting());
}
}
…
}
Settings数据的变化监听是通过ContentObserver来实现的,主要code也是在setUp()里面,在系统设置的数据库中,以下是涉及到Night Light的键值
NIGHT_DISPLAY_ACTIVATED
NIGHT_DISPLAY_COLOR_TEMPERATURE
NIGHT_DISPLAY_AUTO_MODE
NIGHT_DISPLAY_CUSTOM_START_TIME
NIGHT_DISPLAY_CUSTOM_END_TIME
他们值发生变化会执行对应的方法
private void setUp() {
Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
// Listen for external changes to any of the settings.
if (mContentObserver == null) {
mContentObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
final String setting = uri == null ? null : uri.getLastPathSegment();
if (setting != null) {
switch (setting) {
case Secure.NIGHT_DISPLAY_ACTIVATED:
final boolean activated = mNightDisplayTintController .isActivatedSetting();
if (mNightDisplayTintController.isActivatedStateNotSet()
|| mNightDisplayTintController.isActivated() != activated) {
mNightDisplayTintController.setActivated(activated);
}
break;
case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
final int temperature = mNightDisplayTintController
.getColorTemperatureSetting();
if (mNightDisplayTintController.getColorTemperature()
!= temperature) {
mNightDisplayTintController
.onColorTemperatureChanged(temperature);
}
break;
case Secure.NIGHT_DISPLAY_AUTO_MODE:
onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
break;
case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME:
onNightDisplayCustomStartTimeChanged(getNightDisplayCustomStartTimeInternal().getLocalTime());
break;
case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
onNightDisplayCustomEndTimeChanged(getNightDisplayCustomEndTimeInternal().getLocalTime());
break;
…
}
}
}
};
}
//注册相应的键值
final ContentResolver cr = getContext().getContentResolver();
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_ACTIVATED),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_AUTO_MODE),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_START_TIME),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
…
if (mNightDisplayTintController.isAvailable(getContext())) {
// Reset the activated state.
mNightDisplayTintController.setActivated(null);
// Prepare the night display color transformation matrix.
mNightDisplayTintController
.setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
mNightDisplayTintController
.setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
// Initialize the current auto mode.
onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
// Force the initialization of the current saved activation state.
if (mNightDisplayTintController.isActivatedStateNotSet()) {
mNightDisplayTintController
.setActivated(mNightDisplayTintController.isActivatedSetting());
}
}
...
}
当设置NightLight中,通过滑动条调节色温,修改了系统设置中的NIGHT_DISPLAY_COLOR_TEMPERATURE值后,这个observer就会触发NIGHT_DISPLAY_COLOR_TEMPERATURE的onChange,最后通过调用NightDisplayTintController的onColorTemperatureChanged方法来处理,这个方法在前一节讲过,它是实现屏幕Night light效果的核心,通过这种机制可以保证滑动条调节色温,设置的Night light效果马上生效。
三 总结
本文分析了Android10 Night Light实现细节,根据java面向对象语言的特性,我们要掌握封装成类的关键方法,以及众多类与类、类与接口之间复杂的继承和实现关系。还有android基础概念和特有的一些机制。
1.App层
preference
preferenceScreen
fragement
layout
事件
ContentObserver(封装成NightDisplayListener)
Callback接口(封装成NightDisplayListener)
对framework层Night Light功能接口调用
2.Framework层
SystemService (ColorDisplayService)
AIDL
Night Light功能接口实现
Settings数据存储方式(ContentProvide,资源配置xml)
Handler
ColorTemperature
Matrix算法
SurfaceFlinger算法
binder
ContentObserver