Android10 Night Light实现细节分析

目录

写作目的

定义

读者

一  Applications层部分

1.Night Light入口菜单

 2.Night Light主界面

2.1 Night Light主界面布局

2.2 Schedule Dropdown preference

2.3 Start time preference

2.4 End time preference

2.5 Color Temperature seekbar preference

2.6 Night display activation preference

2.7 Night display footer categor

3.Night Light的ConditionalCard

二 Framework层部分

1.isNightDisplayAvailable / getMinimumColorTemperature / getMaximumColorTemperature

2.isNightDisplayActivated / setNightDisplayActivated

3.getNightDisplayAutoMode/setNightDisplayAutoMode / getNightDisplayCustomStartTime/setNightDisplayCustomStartTime / getNightDisplayCustomEndTime/ setNightDisplayCustomEndTime

4.setNightDisplayColorTemperature

4.1 调用时序图

4.2 framework层的关键方法实现和调用流程

5.开机Night Light效果加载以及Settings数据变化监听

三 总结

1.App层

2.Framework层


写作目的

本文从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());
}

滑动条滑动过程中,将滑动条的值转换成色温值,通过这个色温值参数设置屏幕当前的夜间模式效果,setNightDisplayColorTemperatureNight 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实现细节,需要了解的可以看下这篇文章:

https://blog.csdn.net/QWE123ZXCZ/article/details/84316484?ops_request_misc=&request_id=&biz_id=102&utm_term=@SystemService&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-2-84316484

这里不做重点讲述。

我们讲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 appapp ,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

 

 

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值