1.简介
- 顾名思义,quick settings就是快速设置setting的,下拉状态栏就可以看到,拉到底可以显示一页的功能按钮,有两页,可以左右滑动切换,
- 另外底部右侧还有个编译按钮,点击进去是支持快速设置的所有功能,可以选择显示哪些,以及拖拽移动位置。


1.1.CentralSurfacesImpl.java
>1.makeStatusBarView
- 核心就是把QSFragment加载到qs_frame容器里
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
//...
// 初始化 quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
//这里就是把容器替换成Fragment了,逻辑见1.2.1
ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
mExtensionController
.newExtension(QS.class)//返回一个builder
.withPlugin(QS.class)//传递数据
.withDefault(this::createDefaultQSFragment)//见补充2
.build());//创建controller
mBrightnessMirrorController = new BrightnessMirrorController(
mNotificationShadeWindowView,
mNotificationPanelViewController,
mNotificationShadeDepthControllerLazy.get(),
mBrightnessSliderFactory,
(visible) -> {
mBrightnessMirrorVisible = visible;
updateScrimController();
});
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
mQSPanelController = ((QSFragment) qs).getQSPanelController();
((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController);
}
});
}
>2.createDefaultQSFragment
可以看到加载的就是QSFragment
protected QS createDefaultQSFragment() {
return FragmentHostManager.get(mNotificationShadeWindowView).create(QSFragment.class);
}
1.2.ExtensionFragmentListener
>1.attachExtensonToFragment
public static <T> void attachExtensonToFragment(View view, String tag, int id,
Extension<T> extension) {//见补充2
extension.addCallback(new ExtensionFragmentListener(view, tag, id, extension));
}
>2.构造方法
public class ExtensionFragmentListener<T extends FragmentBase> implements Consumer<T> {
//...
private ExtensionFragmentListener(View view, String tag, int id, Extension<T> extension) {
mTag = tag;
mFragmentHostManager = FragmentHostManager.get(view);
mExtension = extension;
mId = id;
//添加对应的fragment
mFragmentHostManager.getFragmentManager().beginTransaction()
.replace(id, (Fragment) mExtension.get(), mTag)
.commit();
mExtension.clearItem(false);
}
@Override
public void accept(T extension) {
if (Fragment.class.isInstance(extension)) {
mFragmentHostManager.getExtensionManager().setCurrentExtension(mId, mTag,
mOldClass, extension.getClass().getName(), mExtension.getContext());
mOldClass = extension.getClass().getName();
}
mExtension.clearItem(true);
}
1.3.ExtensionControllerImpl
public class ExtensionControllerImpl implements ExtensionController {
>1.构造方法
通过注解实例化对象的
@Inject
public ExtensionControllerImpl(
Context context,
LeakDetector leakDetector,
PluginManager pluginManager,
TunerService tunerService,
ConfigurationController configurationController) {
>2.newExtension
//这里的T就是QS.class
public <T> ExtensionBuilder<T> newExtension(Class<T> cls) {
return new ExtensionBuilder<>();
}
1.4..ExtensionBuilder
小节1.3的内部类
private class ExtensionBuilder<T> implements ExtensionController.ExtensionBuilder<T> {
//builder 最终返回的就是这个对象
private ExtensionImpl<T> mExtension = new ExtensionImpl<>();
>1.withPlugin
public <P extends T> ExtensionController.ExtensionBuilder<T> withPlugin(Class<P> cls) {
return withPlugin(cls, PluginManager.Helper.getAction(cls));
}
public <P> ExtensionController.ExtensionBuilder<T> withPlugin(Class<P> cls,
String action, PluginConverter<T, P> converter) {
mExtension.addPlugin(action, cls, converter);//交给1.5处理了
return this;
}
>2.withDefault
public ExtensionController.ExtensionBuilder<T> withDefault(Supplier<T> def) {
mExtension.addDefault(def);//交给1.5处理了
return this;
}
>3.build
public ExtensionController.Extension<T> build() {
// Sort items in ascending order
Collections.sort(mExtension.mProducers, Comparator.comparingInt(Item::sortOrder));
mExtension.notifyChanged();// 这里会获取item
return mExtension;
}
1.5.ExtensionImpl
private class ExtensionImpl<T> implements ExtensionController.Extension<T> {
private final ArrayList<Item<T>> mProducers = new ArrayList<>();
private final ArrayList<Consumer<T>> mCallbacks = new ArrayList<>();
private T mItem;
private Context mPl
//...
>1.addDefault
public void addDefault(Supplier<T> def) {
mProducers.add(new Default(def));
}
>2.addPlugin
public <P> void addPlugin(String action, Class<P> cls, PluginConverter<T, P> converter) {
mProducers.add(new PluginItem(action, cls, converter));
}
>4.addCallback
就是2.4.1的回调
public void addCallback(Consumer<T> callback) {
mCallbacks.add(callback);
}
>5.notifyChanged
private void notifyChanged() {
if (mItem != null) {
mLeakDetector.trackGarbage(mItem);
}
mItem = null;
for (int i = 0; i < mProducers.size(); i++) {
final T item = mProducers.get(i).get();
if (item != null) {
mItem = item;//mItem获取
break;
}
}
for (int i = 0; i < mCallbacks.size(); i++) {
//回调见1.2,也就是把通过addDefault提供的fragment返回给回调
mCallbacks.get(i).accept(mItem);
}
}
>6.Default
public Default(Supplier<T> supplier) {
mSupplier = supplier;
}
@Override
public T get() {
return mSupplier.get();//这里返回的就是那个fragment
}
>7.待定
//...PluginItem默认的mItem是空的,只有调用下边方法以后才有值
public void onPluginConnected(P plugin, Context pluginContext) {
mPluginContext = pluginContext;
if (mConverter != null) {
mItem = mConverter.getInterfaceFromPlugin(plugin);
} else {
mItem = (T) plugin;
}
notifyChanged();
}
2.QSFragment
2.1.onCreateView
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
try {//布局见2.2
return inflater.inflate(R.layout.qs_panel, container, false);
}
}
2.2.qs_pannel.xml
## 帧布局
<com.android.systemui.qs.QSContainerImpl
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:clipChildren="false">
<com.android.systemui.qs.NonInterceptingScrollView
android:id="@+id/expanded_qs_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="@dimen/qs_panel_elevation"
android:importantForAccessibility="no"
android:scrollbars="none"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_weight="1">
<!--自定义线性布局,见小节3-->
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:focusable="true"
android:accessibilityTraversalBefore="@android:id/edit"
android:clipToPadding="false"
android:clipChildren="false">
<!-- 见补充1-->
<include layout="@layout/qs_footer_impl" />
</com.android.systemui.qs.QSPanel>
</com.android.systemui.qs.NonInterceptingScrollView>
<!--补充2-->
<include layout="@layout/quick_status_bar_expanded_header" />
<include
layout="@layout/footer_actions"
android:id="@+id/qs_footer_actions"
android:layout_height="@dimen/footer_actions_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
/>
<!--这个是qs选项的编辑页面,见小节5-->
<include
android:id="@+id/qs_customize"
layout="@layout/qs_customize_panel"
android:visibility="gone" />
</com.android.systemui.qs.QSContainerImpl>
>1.qs_footer_impl.xml
红框部分,中间的indicator和右侧的edit按钮

<!-- Extends FrameLayout -->
<com.android.systemui.qs.QSFooterView
android:id="@+id/qs_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_footer_margin"
android:layout_marginEnd="@dimen/qs_footer_margin"
android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
android:background="@android:color/transparent"
android:baselineAligned="false"
android:clickable="false"
android:clipChildren="false"
android:clipToPadding="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/qs_footer_height"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/build"
android:layout_width="0dp"
android:layout_height="match_parent"
android:paddingEnd="4dp"
android:layout_weight="1"
android:clickable="true"
android:ellipsize="marquee"
android:focusable="true"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.QS.Status.Build"
android:visibility="gone" />
<!--自定义的viewgroup,就是中间那白点灰点指示器-->
<com.android.systemui.qs.PageIndicator
android:id="@+id/footer_page_indicator"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:visibility="gone" />
<!--右侧的编译按钮-->
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@android:id/edit"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_gravity="center_vertical|end"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:contentDescription="@string/accessibility_quick_settings_edit"
android:focusable="true"
android:padding="@dimen/qs_footer_icon_padding"
android:src="@*android:drawable/ic_mode_edit"
android:tint="?android:attr/textColorPrimary" />
</FrameLayout>
</LinearLayout>
</com.android.systemui.qs.QSFooterView>
>2.quick_status_bar_expanded_header.xml
红色部分就是

展开状态
需要注意的是,下边布局里的2个include布局,模拟器上并未用到。
<!-- Extends FrameLayout -->
<com.android.systemui.qs.QuickStatusBarHeader
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:background="@android:color/transparent"
android:importantForAccessibility="no"
android:baselineAligned="false"
android:clickable="false"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="0dp"
android:paddingEnd="0dp"
android:paddingStart="0dp"
android:elevation="4dp" >
<!-- 不可见..Date and privacy. Only visible in QS when not in split shade -->
<include layout="@layout/quick_status_bar_header_date_privacy"/>
<RelativeLayout
android:id="@+id/qs_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:clipChildren="false"
android:clipToPadding="false">
<!-- 不可见...Time, icons and Carrier (only in QS when not in split shade) -->
<include layout="@layout/quick_qs_status_icons"/>
//这个是图上黄色部分
<com.android.systemui.qs.QuickQSPanel
android:id="@+id/quick_qs_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/quick_qs_status_icons"
android:layout_marginTop="@dimen/qqs_layout_margin_top"
android:accessibilityTraversalAfter="@id/quick_qs_status_icons"
android:clipChildren="false"
android:clipToPadding="false"
android:focusable="true"
android:paddingBottom="@dimen/qqs_layout_padding_bottom"
android:importantForAccessibility="no">
</com.android.systemui.qs.QuickQSPanel>
</RelativeLayout>
</com.android.systemui.qs.QuickStatusBarHeader>
>3.combined_qs_header.xml
- 这个布局是个MotionLayout,方便动画,下拉显示的状态栏用到的就是这个布局,见2.7

<com.android.systemui.util.NoRemeasureMotionLayout
android:id="@+id/split_shade_status_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/large_screen_shade_header_min_height">
//...
<!--时间
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
/>
<!--日期
<com.android.systemui.statusbar.policy.VariableDateView
android:id="@+id/date"
/>
<!--移动公司信息
<include
android:id="@+id/carrier_group"
layout="@layout/qs_carrier_group"
/>
<!--图标
<com.android.systemui.statusbar.phone.StatusIconContainer
android:id="@+id/statusIcons"
/>
<!--电池
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/batteryRemainingIcon"
/>
>4.qs_carrier_group.xml
- carrier1/2/3显示移动运营商的信息,一张sim卡的就显示一个。
- no_carrier_text是没有sim卡或者飞行模式的时候显示的文本控件
<!-- Extends LinearLayout -->
<com.android.systemui.qs.carrier.QSCarrierGroup
android:id="@+id/qs_mobile"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="end|center_vertical"
android:orientation="horizontal">
<!--没有sim卡或者飞行模式的时候显示的内容
<com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/no_carrier_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.QS.Status.Carriers.NoCarrierText"
android:textDirection="locale"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:visibility="gone"/>
<!-- sim卡信息,最多可以显示3个,应该就是我们平时说的双卡吧,不过这里有3个,没听过3卡-->
<include
layout="@layout/qs_carrier"
android:id="@+id/carrier1"
android:layout_weight="1"/>
<View
android:id="@+id/qs_carrier_divider1"
android:layout_width="@dimen/qs_header_carrier_separator_width"
android:layout_height="match_parent"
android:visibility="gone"
android:importantForAccessibility="no"/>
<include
layout="@layout/qs_carrier"
android:id="@+id/carrier2"
android:layout_weight="1"
android:visibility="gone"/>
<View
android:id="@+id/qs_carrier_divider2"
android:layout_width="@dimen/qs_header_carrier_separator_width"
android:layout_height="match_parent"
android:layout_weight="1"
android:visibility="gone"
android:importantForAccessibility="no"/>
<include
layout="@layout/qs_carrier"
android:id="@+id/carrier3"
android:layout_weight="1"
android:visibility="gone"/>
</com.android.systemui.qs.carrier.QSCarrierGroup>
>5.qs_carrier.xml
就是补充4里include的布局
<com.android.systemui.qs.carrier.QSCarrier
android:id="@+id/linear_carrier" >
<com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/qs_carrier_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.QS.Status.Carriers"
android:textDirection="locale"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:maxEms="7"/>
<View
android:id="@+id/spacer"
android:layout_width="@dimen/qs_carrier_margin_width"
android:layout_height="match_parent"
android:visibility="gone"
/>
<include
layout="@layout/mobile_signal_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_carrier_margin_width"
android:visibility="gone" />
</com.android.systemui.qs.carrier.QSCarrier>
>6.footer_actions.xml
就是快速设置容器完全可见的时候,底部的功能,包括设置,电源,头像等,如图

<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/footer_actions_height"
android:elevation="@dimen/qs_panel_elevation"
android:paddingTop="@dimen/qs_footer_actions_top_padding"
android:paddingBottom="@dimen/qs_footer_actions_bottom_padding"
android:background="@drawable/qs_footer_actions_background"
android:gravity="center_vertical|end"
android:layout_gravity="bottom"
/>
>7.qs_customize_panel.xml
qs的编辑页面
<!-- Height is 0 because it will be managed by the QS manually -->
<com.android.systemui.qs.customize.QSCustomizer
android:layout_width="match_parent"
android:layout_height="0dp"
android:elevation="4dp"
android:orientation="vertical"
android:gravity="center_horizontal">
</com.android.systemui.qs.customize.QSCustomizer>
2.3.onViewCreated
核心方法都在这里了,基本都是各种controller来控制,后边细看
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
mQSPanelController = qsFragmentComponent.getQSPanelController();
mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
mQSPanelController.init();
mQuickQSPanelController.init();
//底部多用户按钮,设置按钮,电源按钮的部分,见2.5
mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
this);
LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions);
FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
mListeningAndVisibilityLifecycleOwner);
mFooterActionsController.init();
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
mQSPanelScrollView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
updateQsBounds();
});
mQSPanelScrollView.setOnScrollChangeListener(
(v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
mQSAnimator.requestAnimatorUpdate();
mHeader.setExpandedScrollAmount(scrollY);
if (mScrollListener != null) {
mScrollListener.onQsPanelScrollChanged(scrollY);
}
});
mHeader = view.findViewById(R.id.header);
mFooter = qsFragmentComponent.getQSFooter();
mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
mQSContainerImplController.init();
mContainer = mQSContainerImplController.getView();
mDumpManager.registerDumpable(mContainer.getClass().getName(), mContainer);
mQSAnimator = qsFragmentComponent.getQSAnimator();
mQSSquishinessController = qsFragmentComponent.getQSSquishinessController();
//这个是qs的编辑页面
mQSCustomizerController = qsFragmentComponent.getQSCustomizerController();
mQSCustomizerController.init();
mQSCustomizerController.setQs(this);
if (savedInstanceState != null) {
setQsVisible(savedInstanceState.getBoolean(EXTRA_VISIBLE));
setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
setEditLocation(view);
mQSCustomizerController.restoreInstanceState(savedInstanceState);
if (mQsExpanded) {
mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState);
}
}
mStatusBarStateController.addCallback(this);
onStateChanged(mStatusBarStateController.getState());
view.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
if (sizeChanged) {
setQsExpansion(mLastQSExpansion, mLastPanelFraction,
mLastHeaderTranslation, mSquishinessFraction);
}
});
mQSPanelController.setUsingHorizontalLayoutChangeListener(
() -> {
mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f);
mQSAnimator.requestAnimatorUpdate();
});
}
2.4.FooterActionsViewBinder
主要就是给线性布局里添加需要的按钮,用户切换,settings,电源按钮等
>1.bind
fun bind(
view: LinearLayout,
viewModel: FooterActionsViewModel,
qsVisibilityLifecycleOwner: LifecycleOwner,
) {
//...
//创建并添加用户切换按钮
val userSwitcherHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = false)
//创建并添加设置按钮
val settingsHolder =
IconButtonViewHolder.createAndAdd(inflater, view, isLast = viewModel.power == null)
// 给设置按钮绑定数据
bindButton(settingsHolder, viewModel.settings)
if (viewModel.power != null) {
//创建并添加电源按钮
val powerHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = true)
//电源按钮绑定数据
bindButton(powerHolder, viewModel.power)
}
var previousUserSwitcher: FooterActionsButtonViewModel? = null
//...
// User switcher.
launch {
viewModel.userSwitcher.collect { userSwitcher ->
//用户数据是可变的,所以这里收集流的变化,动态绑定用户数据
if (previousUserSwitcher != userSwitcher) {
bindButton(userSwitcherHolder, userSwitcher)
previousUserSwitcher = userSwitcher
}
}
}
>2.bindButton
private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) {
val buttonView = button.view
//设置id,可见性,model为null的时候没有id的
buttonView.id = model?.id ?: View.NO_ID
buttonView.isVisible = model != null
if (model == null) {
return
}
val backgroundResource =
buttonView.setBackgroundResource(backgroundResource)
//点击事件
buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
val icon = model.icon
val iconView = button.icon
//按钮绑定图标
IconViewBinder.bind(icon, iconView)
if (model.iconTint != null) {
iconView.setColorFilter(model.iconTint, PorterDuff.Mode.SRC_IN)
} else {
iconView.clearColorFilter()
}
}
}
2.5.FooterActionsViewModel.kt
每个按钮的数据保存在这个类里,包括id,图标,点击事件,颜色等
data class FooterActionsButtonViewModel(
val id: Int,
val icon: Icon,
@ColorInt val iconTint: Int?,
@AttrRes val backgroundColor: Int,
val onClick: (Expandable) -> Unit,
)
>1.对象创建
看下fragment里的viewmodel对象生成的方法,通过factory生成的,factory是注解实例化的
mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create( this);
现在看下我们关心的几个按钮
>2.userSwitcher
用户切换按钮是个flow,会变化的,需要collect
val userSwitcher: Flow<FooterActionsButtonViewModel?> =
footerActionsInteractor.userSwitcherStatus
.map { userSwitcherStatus ->
when (userSwitcherStatus) {
//多用户没有打开的话,这个是空
UserSwitcherStatusModel.Disabled -> null
is UserSwitcherStatusModel.Enabled -> {
if (userSwitcherStatus.currentUserImage == null) {
//用户头像没有的话返回空
return@map null
}
//这里是按钮的数据了
userSwitcherButton(userSwitcherStatus)
}
}
}
.distinctUntilChanged()
private fun userSwitcherButton(
status: UserSwitcherStatusModel.Enabled
): FooterActionsButtonViewModel {
val icon = status.currentUserImage!!
return FooterActionsButtonViewModel(
id = R.id.multi_user_switch,
icon =
Icon.Loaded(
icon,
ContentDescription.Loaded(
userSwitcherContentDescription(status.currentUserName)
),
),
iconTint = null,
backgroundColor = R.attr.offStateColor,
onClick = this::onUserSwitcherClicked,
)
}
>3.settings按钮
val settings: FooterActionsButtonViewModel =
FooterActionsButtonViewModel(
id = R.id.settings_button_container,
Icon.Resource(
R.drawable.ic_settings,
ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
),
iconTint = null,
backgroundColor = R.attr.offStateColor,
this::onSettingsButtonClicked,
)
>4.power按钮
val power: FooterActionsButtonViewModel? =
if (showPowerButton) {
FooterActionsButtonViewModel(
id = R.id.pm_lite,
Icon.Resource(
android.R.drawable.ic_lock_power_off,
ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
),
iconTint =
Utils.getColorAttrDefaultColor(
context,
com.android.internal.R.attr.textColorOnAccent,
),
backgroundColor = com.android.internal.R.attr.colorAccent,
this::onPowerButtonClicked,
)
} else {
null
}
>5.按钮可见性控制
- 我们可以通过id查找到对应的view,完事修改可见性。
- 需要注意的是,用户切换按钮可能没有id的,如果设置里没有打开多用户的话。
2.6.QuickStatusBarHeader.java
public class QuickStatusBarHeader extends FrameLayout {
对里边包含的view的基本属性的处理,平移动画,透明度动画等。
>1.onFinishInflate
protected void onFinishInflate() {
super.onFinishInflate();
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
//下边两个,就是上边布局里的2个include的容器
mDatePrivacyView = findViewById(R.id.quick_status_bar_date_privacy);
mStatusIconsView = findViewById(R.id.quick_qs_status_icons);
//...
}
>2.updateResources
void updateResources() {
//..可以看到mUseCombinedQSHeader这个为true的情况下,那2个include的布局都是gone,
boolean gone = largeScreenHeaderActive || mUseCombinedQSHeader || mQsDisabled;
mStatusIconsView.setVisibility(gone ? View.GONE : View.VISIBLE);
mDatePrivacyView.setVisibility(gone ? View.GONE : View.VISIBLE);
2.7.状态栏的东西咋显示
上一篇小节1.3的status_bar_expanded.xml布局里有个id是qs_header_stub的ViewStub。
>1.getLargeScreenShadeHeaderBarView
StatusBarViewModule.java
@Provides
@Named(LARGE_SCREEN_SHADE_HEADER)
@CentralSurfacesComponent.CentralSurfacesScope
public static View getLargeScreenShadeHeaderBarView(
NotificationShadeWindowView notificationShadeWindowView,
FeatureFlags featureFlags) {
ViewStub stub = notificationShadeWindowView.findViewById(R.id.qs_header_stub);
int layoutId = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)//返回true
? R.layout.combined_qs_header
: R.layout.large_screen_shade_header;
stub.setLayoutResource(layoutId);
View v = stub.inflate();
return v;
}
这个flag是enabled
@JvmField val COMBINED_QS_HEADERS = releasedFlag(501, "combined_qs_headers")
>2.LargeScreenShadeHeaderController.kt
看下@Named注解,会调用补充1的方法生成对象
@CentralSurfacesScope
class LargeScreenShadeHeaderController @Inject constructor(
@Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
//..
) : ViewController<View>(header), Dumpable {//header传递给父类
2.8.其他
>1.NonInterceptingScrollView
禁止拦截可能引起滚动的触摸事件
/**
* ScrollView that disallows intercepting for touches that can cause scrolling.
*/
public class NonInterceptingScrollView extends ScrollView {
>2.QuickQSPanel
- 控制类QuickQSPanelController.java
- 相关布局参考2.2.2,图上黄色的部分,也就是那4个qqs按钮
- 这个在拉到底通知栏不可见的时候也会被隐藏掉,显示8个setting的叫QSPanel,可以左右翻页的。
public class QuickQSPanel extends QSPanel {
3.QSPanel
public class QSPanel extends LinearLayout implements Tunable {
3.1.setBrightnessView
添加亮度调节控件在第一个位置,调用的地方在上边的QSPannelController
public void setBrightnessView(@NonNull View view) {
//..
addView(view, 0);//index是0,可以看到调节亮度的控件是加在最上方的
mBrightnessView = view;
setBrightnessViewMargin();
}
3.2.setFooterPageIndicator
设置底部的圆点指示器
public void setFooterPageIndicator(PageIndicator pageIndicator) {
if (mTileLayout instanceof PagedTileLayout) {
mFooterPageIndicator = pageIndicator;
updatePageIndicator();
}
}
>1.PageIndicator.java
public void setNumPages(int numPages) {
//修改可见性
setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
if (numPages == getChildCount()) {
return;
}
//把多余的删除了
while (numPages < getChildCount()) {
removeViewAt(getChildCount() - 1);
}
//添加dot
while (numPages > getChildCount()) {
ImageView v = new ImageView(mContext);
v.setImageResource(R.drawable.minor_a_b);
v.setImageTintList(mTint);
addView(v, new LayoutParams(mPageIndicatorWidth, mPageIndicatorHeight));
}
// Refresh state.
setIndex(mPosition >> 1);
requestLayout();
}
3.3.initialize
void initialize(QSLogger qsLogger) {
//补充1
mTileLayout = getOrCreateTileLayout();
//有音乐播放的时候,会多一个播放器控件显示
if (mUsingMediaPlayer) {
mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
mHorizontalLinearLayout.setVisibility(
//..
mHorizontalContentContainer = new RemeasuringLinearLayout(mContext);
mHorizontalContentContainer.setOrientation(LinearLayout.VERTICAL);
setHorizontalContentContainerClipping();
LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
//..
mHorizontalLinearLayout.addView(mHorizontalContentContainer, lp);
lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1);
addView(mHorizontalLinearLayout, lp);
}
}
>1.getOrCreateTileLayout
public QSTileLayout getOrCreateTileLayout() {
if (mTileLayout == null) {
mTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
.inflate(R.layout.qs_paged_tile_layout, this, false);
}
return mTileLayout;
}
>2.onFinishInflate
protected void onFinishInflate() {
super.onFinishInflate();
mFooter = findViewById(R.id.qs_footer);
}
3.4.updateResources
public void updateResources() {
updatePadding();
updatePageIndicator();//补充1
setBrightnessViewMargin();
if (mTileLayout != null) {
mTileLayout.updateResources();
}
}
>1.updatePageIndicator
private void updatePageIndicator() {
if (mTileLayout instanceof PagedTileLayout) {
if (mFooterPageIndicator != null) {
mFooterPageIndicator.setVisibility(View.GONE);//初始化是gone
//indicator调用setNumber方法的时候,如果个数大于1,就会设置为可见,见3.6.8
((PagedTileLayout) mTileLayout).setPageIndicator(mFooterPageIndicator);
}
}
}
3.5.addTile
给mTileLayout里添加数据
void addTile(QSPanelControllerBase.TileRecord tileRecord) {
final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
drawTile(tileRecord, state);
}
};
tileRecord.tile.addCallback(callback);
tileRecord.callback = callback;
tileRecord.tileView.init(tileRecord.tile);
tileRecord.tile.refreshState();
if (mTileLayout != null) {
mTileLayout.addTile(tileRecord);//见3.6.1
}
}
3.6..PagedTileLayout
- PagedTileLayout是个新版的ViewPager,页面用的是TileLayout,数据是TileRecord。
- addTile使得mTiles集合数据变化,会重新onMeasure,里边又会重新计算页面数,
- distributeTiles方法里会判断是否要添加新的页面,以及给新的页面添加数据。
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
public class PagedTileLayout extends ViewPager implements QSTileLayout {
private final ArrayList<TileRecord> mTiles = new ArrayList<>(); //存储数据
private final ArrayList<TileLayout> mPages = new ArrayList<>();//存储页面
>1.addTile
public void addTile(TileRecord tile) {
mTiles.add(tile);
forceTilesRedistribution("adding new tile");//补充3
requestLayout();
}
>2.removeTile
public void removeTile(TileRecord tile) {
if (mTiles.remove(tile)) {
requestLayout();
}
}
>3.forceTilesRedistribution
修改重新分配标志为true
public void forceTilesRedistribution(String reason) {
mDistributeTiles = true;
}
>4.onFinishInflate
protected void onFinishInflate() {
super.onFinishInflate();
mPages.add(createTileLayout());//添加第一个页面,添加内容参考补充9
mAdapter.notifyDataSetChanged();
}
>5.onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int nTiles = mTiles.size();
if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec)
|| mLastExcessHeight != mExcessHeight) {
mLastMaxHeight = MeasureSpec.getSize(heightMeasureSpec);
mLastExcessHeight = mExcessHeight;
int availableHeight = mLastMaxHeight - mExcessHeight;
if (mPages.get(0).updateMaxRows(availableHeight, nTiles) || mDistributeTiles) {
mDistributeTiles = false;
distributeTiles();//处理tiles,见补充6
}
final int nRows = mPages.get(0).mRows;
for (int i = 0; i < mPages.size(); i++) {
TileLayout t = mPages.get(i);
t.mRows = nRows;
}
}
>6.distributeTiles
private void distributeTiles() {
emptyAndInflateOrRemovePages();//补充7
final int tilesPerPageCount = mPages.get(0).maxTiles();
int index = 0;
final int totalTilesCount = mTiles.size();
for (int i = 0; i < totalTilesCount; i++) {
TileRecord tile = mTiles.get(i);
if (mPages.get(index).mRecords.size() == tilesPerPageCount) index++;
mPages.get(index).addTile(tile);// 给页面添加数据
}
}
>7.emptyAndInflateOrRemovePages
tiles个数变化的话,页面数也会变化
private void emptyAndInflateOrRemovePages() {
final int numPages = getNumPages();
final int NP = mPages.size();
for (int i = 0; i < NP; i++) {
mPages.get(i).removeAllViews();
}
if (NP == numPages) {//页面数没变化就啥也不做了
return;
}
while (mPages.size() < numPages) {
//页面数不够,创建新的,见补充9
mPages.add(createTileLayout());
}
>8.setPageIndicator
public void setPageIndicator(PageIndicator indicator) {
mPageIndicator = indicator;
mPageIndicator.setNumPages(mPages.size());//这里就会改变可见性的
mPageIndicator.setLocation(mPageIndicatorPosition);
}
>9.createTileLayout
private TileLayout createTileLayout() {
TileLayout page = (TileLayout) LayoutInflater.from(getContext())
.inflate(R.layout.qs_paged_page, this, false);
page.setMinRows(mMinRows);
page.setMaxColumns(mMaxColumns);
page.setSelected(false);
return page;
}
3.7.TileLayout
qs按钮的自定义容器,3.6是个ViewPaper,每页添加的容器就是这个,完事这个容器里再添加各自的qs
public class TileLayout extends ViewGroup implements QSTileLayout {
protected void onLayout(boolean changed, int l, int t, int r, int b) {
layoutTileRecords(mRecords.size(), true /* forLayout */);
}
>1.layoutTileRecords
private void layoutTileRecords(int numRecords, boolean forLayout) {
int row = 0;
int column = 0;
mLastTileBottom = 0;
// Layout each QS tile.
final int tilesToLayout = Math.min(numRecords, mRows * mColumns);
for (int i = 0; i < tilesToLayout; i++, column++) {
if (column == mColumns) {
column = 0;
row++;//一行完事再开启下一行
}
final TileRecord record = mRecords.get(i);
final int top = getRowTop(row);
//left的位置和column有关
final int left = getColumnStart( column);
final int right = left + mCellWidth;
final int bottom = top + record.tileView.getMeasuredHeight();
if (forLayout) {
record.tileView.layout(left, top, right, bottom);
} else {
record.tileView.setLeftTopRightBottom(left, top, right, bottom);
}
record.tileView.setPosition(i);
float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction);
mLastTileBottom = top + (int) (record.tileView.getMeasuredHeight() * scale);
}
}
3.8.QSTileHost
通过注解实例化的,实现了Tunable
public static final String TILES_SETTING = Secure.QS_TILES;//"sysui_qs_tiles"
public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable {
@Inject
public QSTileHost(Context context,
//..
TunerService tunerService,//注解生成的对象,见小节8
//..
//参考3.9
mQsFactories.add(defaultFactory);
mainExecutor.execute(() -> {
//这里会把自己加入到tunerService里,参考8.2
tunerService.addTunable(this, TILES_SETTING);
mAutoTiles = autoTiles.get();
mTileServiceRequestController.init();
});
}
>1.onTuningChanged
此方法是TunerServiceImpl.java里调用的,读取的是settings里保存的值
public void onTuningChanged(String key, String newValue) {
//...
//转换成集合,补充5
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
//...数据未发生变化
if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
//先销毁旧的
mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
tile -> {
tile.getValue().destroy();
});
final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
for (String tileSpec : tileSpecs) {
QSTile tile = mTiles.get(tileSpec);
if (tile != null && (!(tile instanceof CustomTile)
|| ((CustomTile) tile).getUser() == currentUser)) {
//销毁旧的tile
} else {
//销毁旧的tile
try {
tile = createTile(tileSpec);//根据名字创建新的tile,参考3.9.1
if (tile != null) {
tile.setTileSpec(tileSpec);
if (tile.isAvailable()) {
newTiles.put(tileSpec, tile);//可用的tile放到集合里
} else {
tile.destroy();//不可用的话就销毁掉
}
}
}
}
}
mCurrentUser = currentUser;
List<String> currentSpecs = new ArrayList<>(mTileSpecs);
//把 mTileSpecs 和 mTiles 的数据换成有效的tiles
mTileSpecs.clear();
mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles.
mTiles.clear();
mTiles.putAll(newTiles);
if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
changeTilesByUser(currentSpecs, loadTileSpecs(mContext, ""));
} else {
String resolvedTiles = TextUtils.join(",", mTileSpecs);
if (!resolvedTiles.equals(newValue)) {
saveTilesToSettings(mTileSpecs);//保存到settings里
}
mTilesListDirty = false;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onTilesChanged();
}
}
}
>2.getDefaultSpecs
public static List<String> getDefaultSpecs(Context context) {
final ArrayList<String> tiles = new ArrayList<String>();
final Resources res = context.getResources();
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
tiles.addAll(Arrays.asList(defaultTileList.split(",")));
//debug模式多了个dump systemui heap的选项("dbg:mem")
if (Build.IS_DEBUGGABLE
&& GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
}
return tiles;
}
>3.DefaultSpecs数据
- 在查询所有tile数据的时候,把wifi和cell移除了,替换成internet,所以wifi和cell其实没啥用
<string name="quick_settings_tiles_default" translatable="false">
internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.SafetyCenterQsTileService)
</string>
GMS里overlay了
<string name="quick_settings_tiles_default" translatable="false">
internet,bt,dnd,flashlight,rotation,battery,airplane,night,screenrecord,custom(com.google.android.gms/.nearby.sharing.SharingTileService)
</string>
>4.changeTilesByUser
public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
final List<String> copy = new ArrayList<>(previousTiles);
final int NP = copy.size();
for (int i = 0; i < NP; i++) {
String tileSpec = copy.get(i);
//非自定义的,以"custom("开头的tile,不做处理
if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
//新的里边没有旧的custom类型的tile,
if (!newTiles.contains(tileSpec)) {
ComponentName component = CustomTile.getComponentFromSpec(tileSpec);
Intent intent = new Intent().setComponent(component);
TileLifecycleManager lifecycleManager = mTileLifeCycleManagerFactory.create(
intent, new UserHandle(mCurrentUser));
lifecycleManager.onStopListening();
lifecycleManager.onTileRemoved();
mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser));
//把sp里的值改为false
setTileAdded(component, mCurrentUser, false);
lifecycleManager.flushMessagesAndUnbind();
}
}
mTilesListDirty = true;
//保存到本地
saveTilesToSettings(newTiles);
}
>5.loadTileSpecs
- tileList是逗号分开的一堆string,这里转化成集合,并且处理下default,以及internet
- 老版的wifi和cell现在合并成一个internet了,这里做下处理
protected static List<String> loadTileSpecs(Context context, String tileList) {
final Resources res = context.getResources();
if (TextUtils.isEmpty(tileList)) {
//settings里数据是空,加载默认的配置,就是个"default"
tileList = res.getString(R.string.quick_settings_tiles);
}
final ArrayList<String> tiles = new ArrayList<String>();
boolean addedDefault = false;
Set<String> addedSpecs = new ArraySet<>();
for (String tile : tileList.split(",")) {
tile = tile.trim();
if (tile.isEmpty()) continue;
if (tile.equals("default")) {//如果settings里没数据,读取默认配置是"default"
if (!addedDefault) {
//补充2,读取默认配置
List<String> defaultSpecs = getDefaultSpecs(context);
for (String spec : defaultSpecs) {
if (!addedSpecs.contains(spec)) {
tiles.add(spec);
addedSpecs.add(spec);
}
}
addedDefault = true;
}
} else {
if (!addedSpecs.contains(tile)) {
tiles.add(tile);
addedSpecs.add(tile);
}
}
}
if (!tiles.contains("internet")) {
if (tiles.contains("wifi")) {
// Replace the WiFi with Internet, and remove the Cell
tiles.set(tiles.indexOf("wifi"), "internet");
tiles.remove("cell");
} else if (tiles.contains("cell")) {
// Replace the Cell with Internet
tiles.set(tiles.indexOf("cell"), "internet");
}
} else {
tiles.remove("wifi");
tiles.remove("cell");
}
return tiles;
}
3.9.QSFactoryImpl.java
这个类用来创建QSTile的
>1.createTile
public final QSTile createTile(String tileSpec) {
QSTileImpl tile = createTileInternal(tileSpec);//补充2
if (tile != null) {
tile.initialize();
tile.postStale(); // Tile was just created, must be stale.
}
return tile;
}
>2.createTileInternal
根据tile的名字有对应的provider创建对象
protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
switch (tileSpec) {
case "wifi":
return mWifiTileProvider.get();
case "internet":
return mInternetTileProvider.get();
case "bt":
return mBluetoothTileProvider.get();
case "cell":
//..
// 自定义类型的
if (tileSpec.startsWith(CustomTile.PREFIX)) {
return CustomTile.create(
mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
}
>3.createTileView
public QSTileView createTileView(Context context, QSTile tile, boolean collapsedView) {
QSIconView icon = tile.createTileView(context);
return new QSTileViewImpl(context, icon, collapsedView);
}
3.10..QSTileView

public abstract class QSTileView extends LinearLayout {
实现类
open class QSTileViewImpl @JvmOverloads constructor(
context: Context,
private val _icon: QSIconView,
private val collapsed: Boolean = false
) : QSTileView(context), HeightOverrideable, LaunchableView {
init {
setId(generateViewId())
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL or Gravity.START
//...
background = createTileBackground()
setColor(getBackgroundColorForState(QSTile.State.DEFAULT_STATE))
//...
val iconSize = resources.getDimensionPixelSize(R.dimen.qs_icon_size)
//左侧的图标
addView(_icon, LayoutParams(iconSize, iconSize))
//中间的标签
createAndAddLabels()
//右侧图标
createAndAddSideView()
}
4.QSPanelController
4.1.构造方法
@Inject
QSPanelController(QSPanel view, TunerService tunerService,
//...
//亮度控件滑动控制
mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
//添加 亮度调节的view
mView.setBrightnessView(mBrightnessSliderController.getRootView());
//亮度控件控制器
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mView.setUsingCombinedHeaders(featureFlags.isEnabled(COMBINED_QS_HEADERS));
}
4.2. onInit
public void onInit() {
super.onInit();
//...
mQsCustomizerController.init();
mBrightnessSliderController.init();
}
4.3.onViewAttached
protected void onViewAttached() {
super.onViewAttached();
//...
mView.updateResources(); //具体见3.4
if (mView.isListening()) {
refreshAllTiles();
}
switchTileLayout(true);
//获取PagedTileLayout
PagedTileLayout pagedTileLayout= ((PagedTileLayout) mView.getOrCreateTileLayout());
pagedTileLayout.setOnTouchListener(mTileLayoutTouchListener);
}
4.4.showEdit
public void showEdit(View view) {
view.post(() -> {
if (!mQsCustomizerController.isCustomizing()) {
int[] loc = view.getLocationOnScreen();
int x = loc[0] + view.getWidth() / 2;
int y = loc[1] + view.getHeight() / 2;
mQsCustomizerController.show(x, y, false);
}
});
}
4.5..QSPanelControllerBase.java
>1.onViewAttached
protected void onViewAttached() {
//...
setTiles();//补充1
//...
switchTileLayout(true);//补充4
//...
}
>2.setTiles
数据来源是3.8
public void setTiles() {
setTiles(mHost.getTiles(), false);
}
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
if (!collapsedView && mQsTileRevealController != null) {
mQsTileRevealController.updateRevealedTiles(tiles);
}
//...
for (QSTile tile : tiles) {
//如果打算隐藏某个tile,建议放这里,这里是临时的效果,如下隐藏rotation功能
//当然了,这里只隐藏了qs的数据,编辑页面那边还是能看到的。
// if("rotation".equals(tile.getTileSpec())) continue;
addTile(tile, collapsedView);
}
}
>3.addTile
private void addTile(final QSTile tile, boolean collapsedView) {
final TileRecord r =
new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView));
//..这个mView是QSPanel,完事里边又把数据传递给了PagedTileLayout
mView.addTile(r);
mRecords.add(r);
mCachedSpecs = getTilesSpecs();
}
>4.switchTileLayout
boolean switchTileLayout(boolean force) {
if (horizontal != mUsingHorizontalLayout || force) {
//...
mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force);
//...
return true;
}
return false;
}
4.6.编辑按钮点击事件
QSFooterViewController.java 主要看下编辑按钮点击跳转的逻辑是啥
>1.onViewAttached
protected void onViewAttached() {
//...
mEditButton.setOnClickListener(view -> {
//...
mActivityStarter
.postQSRunnableDismissingKeyguard(() -> mQsPanelController.showEdit(view));//见4.4
});
mQsPanelController.setFooterPageIndicator(mPageIndicator);
mView.updateEverything();
}
5.QSCustomizer
这个就是点击qs右下角那个编辑按钮跳转的页面。

public class QSCustomizer extends LinearLayout {
5.1.构造方法
public QSCustomizer(Context context, AttributeSet attrs) {
super(context, attrs);
//加载布局进来
LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar);
// toolbar 设置后退键,
toolbar.setNavigationIcon(
getResources().getDrawable(value.resourceId, mContext.getTheme()));
// toolbar 添加reset菜单
toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
// 设置标题
toolbar.setTitle(R.string.qs_edit);
mRecyclerView = findViewById(android.R.id.list);
mTransparentView = findViewById(R.id.customizer_transparent_view);
5.2.qs_customize_panel_content.xml
- 数据展示就一个RecyclerView处理的
- toolbar就是标题相关的ui了
- 导航栏的背景条是这里显示的
<merge xmlns:android="http://schemas.android.com/apk/res/android">->
<View
android:id="@+id/customizer_transparent_view"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_header_system_icons_area_height"
android:background="@android:color/transparent" />
<com.android.keyguard.AlphaOptimizedLinearLayout
android:id="@+id/customize_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
<Toolbar
android:id="@*android:id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
<View
android:id="@+id/nav_bar_background"
android:layout_width="match_parent"
android:layout_height="@dimen/navigation_bar_size"
android:layout_gravity="bottom"
android:background="#ff000000" />
</merge>
5.3.show hide
>1.show
这里的x,y是那个edit按钮的位置
void show(int x, int y, TileAdapter tileAdapter) {
if (!isShown) {
mRecyclerView.getLayoutManager().scrollToPosition(0);
int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
mX = x - containerLocation[0];
mY = y - containerLocation[1];
isShown = true;
mOpening = true;
setVisibility(View.VISIBLE);
//clipper就是那个动画类
long duration = mClipper.animateCircularClip(
mX, mY, true, new ExpandAnimatorListener(tileAdapter));
mQsContainerController.setCustomizerAnimating(true);
mQsContainerController.setCustomizerShowing(true, duration);
}
}
>2.hide
public void hide(boolean animate) {
if (isShown) {
isShown = false;
mClipper.cancelAnimator();
mOpening = false;
long duration = 0;
if (animate) {
//有动画的话是在参数里的listener里设置为gone的
duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
} else {
setVisibility(View.GONE);
}
mQsContainerController.setCustomizerAnimating(animate);
mQsContainerController.setCustomizerShowing(false, duration);
}
}
>3.showImmediately
void showImmediately() {
if (!isShown) {
mRecyclerView.getLayoutManager().scrollToPosition(0);
setVisibility(VISIBLE);
mClipper.cancelAnimator();
mClipper.showBackground();
isShown = true;
setCustomizing(true);
mQsContainerController.setCustomizerAnimating(false);
mQsContainerController.setCustomizerShowing(true);
}
}
5.4. 高度哪里设置的
- 参考2.2布局,这个控件默认的可见性是gone,高度还是0(见补充1),
- 可见性的话,上边show,hide方法的时候会修改,
- 可是高度是哪里修改的了?在根容器QSContainerImpl.java,如下
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//...
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
//QSCustomizer永远是屏幕的高度,不过需要在其他测量以后,避免修改QS的高度
mQSCustomizer.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
}
>1.qs_customize_panel.xml
<com.android.systemui.qs.customize.QSCustomizer
android:layout_width="match_parent"
android:layout_height="0dp"
android:elevation="4dp"
android:orientation="vertical"
android:gravity="center_horizontal">
</com.android.systemui.qs.customize.QSCustomizer>
5.5.QSCustomizerController.java
>1.show
public void show(int x, int y, boolean immediate) {
if (!mView.isShown()) {
setTileSpecs();
if (immediate) {
mView.showImmediately();//见5.3.3
} else {
mView.show(x, y, mTileAdapter);//见5.3.1
}
//查找所有的qs数据并设置给adapter,
mTileQueryHelper.queryTiles(mQsTileHost);
mKeyguardStateController.addCallback(mKeyguardCallback);
mView.updateNavColors(mLightBarController);
}
}
>2.reset菜单点击事件
这个只能恢复对外展示的tiles,剩余tiles的位置并不一定能恢复。
private final OnMenuItemClickListener mOnMenuItemClickListener = new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == MENU_RESET) {
reset();
}
return false;
}
};
private void reset() {
//数据见3.8.2,reset方法见7.2
mTileAdapter.resetTileSpecs(QSTileHost.getDefaultSpecs(getContext()));
}
>3.hide
public void hide() {
final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
if (mView.isShown()) {
mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED);
mToolbar.dismissPopupMenus();
mView.setCustomizing(false);
save();//退出的时候保存修改的数据
mView.hide(animate);
mView.updateNavColors(mLightBarController);
mKeyguardStateController.removeCallback(mKeyguardCallback);
}
}
>4.save
private void save() {
if (mTileQueryHelper.isFinished()) {
mTileAdapter.saveSpecs(mQsTileHost);
}
}
>5.onViewAttached
protected void onViewAttached() {
mView.updateNavBackDrop(getResources().getConfiguration(), mLightBarController);
mConfigurationController.addCallback(mConfigurationListener);
//小节6用到的listener就是这个adapter
mTileQueryHelper.setListener(mTileAdapter);
int halfMargin =
getResources().getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2;
mTileAdapter.changeHalfMargin(halfMargin);
RecyclerView recyclerView = mView.getRecyclerView();
recyclerView.setAdapter(mTileAdapter);
mTileAdapter.getItemTouchHelper().attachToRecyclerView(recyclerView);
//...
layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
recyclerView.setLayoutManager(layout);
recyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
recyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration());
mToolbar.setOnMenuItemClickListener(mOnMenuItemClickListener);
mToolbar.setNavigationOnClickListener(v -> hide());
}
6.TileQueryHelper
6.1.queryTiles
点击edit按钮,显示qscustom的时候,会调用下边的方法。
public void queryTiles(QSTileHost host) {
mTiles.clear();
mSpecs.clear();
mFinished = false;
// Enqueue jobs to fetch every system tile and then ever package tile.
addCurrentAndStockTiles(host);
}
>1.addCurrentAndStockTiles
private void addCurrentAndStockTiles(QSTileHost host) {
//这个是本地存储的,所有支持的qs,见补充2
String stock = mContext.getString(R.string.quick_settings_tiles_stock);
//这个是对外展示的数据,可能为空,如果用户没进过编辑页面
String current = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.QS_TILES);
final ArrayList<String> possibleTiles = new ArrayList<>();
if (current != null) {
//保存的数据不为null,先添加保存的数据,对外显示的数据添加在前边
possibleTiles.addAll(Arrays.asList(current.split(",")));
} else {
current = "";
}
String[] stockSplit = stock.split(",");
//这是把其他不对外展示的数据加到集合里
for (String spec : stockSplit) {
if (!current.contains(spec)) {
possibleTiles.add(spec);
}
}
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
//移除cell和wifi,这个是兼容旧数据,现在合并成internet了
possibleTiles.remove("cell");
possibleTiles.remove("wifi");
//根据string获取对应的QSTile,加入集合,排除自定义的,不可用的tile
for (String spec : possibleTiles) {
//排除自定义的tile ,PREFIX = "custom(";
if (spec.startsWith(CustomTile.PREFIX)) continue;
final QSTile tile = host.createTile(spec);
if (tile == null) {
continue;
} else if (!tile.isAvailable()) {
tile.setTileSpec(spec);
tile.destroy();
continue;
}
tile.setTileSpec(spec);
tilesToAdd.add(tile);
}
//titlesAdd包含显示的,不显示的,排除一些不能创建tile的spec,见6.5
new TileCollector(tilesToAdd, host).startListening();
}
>2.quick_settings_tiles_stock
- 系统自带的tiles,顺序应该匹配"quick_settings_tiles_default"
<string name="quick_settings_tiles_stock" translatable="false">
internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream
</string>
6.2.addTile
把初始化完成的tile都加入集合里
private void addTile(
String spec, @Nullable CharSequence appLabel, State state, boolean isSystem) {
if (mSpecs.contains(spec)) {
return;
}
state.dualTarget = false; // No dual targets in edit.
state.expandedAccessibilityClassName = Button.class.getName();
state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel))
? null : appLabel;
TileInfo info = new TileInfo(spec, state, isSystem);
mTiles.add(info);
mSpecs.add(spec);
}
6.3.addPackageTiles
- 这个就是动态添加的tile的逻辑
- 主要通过服务的Action过滤支持Tile的组件
private void addPackageTiles(final QSTileHost host) {
mBgExecutor.execute(() -> {
Collection<QSTile> params = host.getTiles();
PackageManager pm = mContext.getPackageManager();
//查找所有注册了这个action的服务
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(TileService.ACTION_QS_TILE), 0, mUserTracker.getUserId());
String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
for (ResolveInfo info : services) {
String packageName = info.serviceInfo.packageName;
ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
//默认的tiles里已经有这个服务组件了
if (stockTiles.contains(componentName.flattenToString())) {
continue;
}
final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
String spec = CustomTile.toSpec(componentName);
State state = getState(params, spec);
if (state != null) {
//能找到对应的state,加入集合,见6.2
addTile(spec, appLabel, state, false);
continue;
}
//服务组件的图标或者应用的图标为空
if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
continue;
}
Drawable icon = info.serviceInfo.loadIcon(pm);
//没有权限
if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
continue;
}
if (icon == null) {
continue;
}
icon.mutate();//复制一份图片
icon.setTint(mContext.getColor(android.R.color.white));
CharSequence label = info.serviceInfo.loadLabel(pm);
//补充1
createStateAndAddTile(spec, icon, label != null ? label.toString() : "null",
appLabel);
}
notifyTilesChanged(true);
});
}
>1.createStateAndAddTile
创建状态对象并加入集合
private void createStateAndAddTile(
String spec, Drawable drawable, CharSequence label, CharSequence appLabel) {
QSTile.State state = new QSTile.State();
state.state = Tile.STATE_INACTIVE;
state.label = label;
state.contentDescription = label;
state.icon = new DrawableIcon(drawable);
addTile(spec, appLabel, state, false);
}
6.4.notifyTilesChanged
private void notifyTilesChanged(final boolean finished) {
final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles);
mMainExecutor.execute(() -> {
if (mListener != null) {
//mlistener就是TileAdapter
mListener.onTilesChanged(tilesToReturn);
}
mFinished = finished;
});
}
6.5.TileCollector
>1.构造方法
构造方法里把QSTile转化为TilePair,就多了个ready字段表示tile是否处理好
TileCollector(List<QSTile> tilesToAdd, QSTileHost host) {
for (QSTile tile: tilesToAdd) {
TilePair pair = new TilePair(tile);
mQSTileList.add(pair);
}
mQSTileHost = host;
if (tilesToAdd.isEmpty()) {
mBgExecutor.execute(this::finished);
}
}
>2.startListening
startListening里每个tile都添加监听并开始初始化
private void startListening() {
for (TilePair pair: mQSTileList) {
pair.mTile.addCallback(this);
pair.mTile.setListening(this, true);
// Make sure that at least one refresh state happens
pair.mTile.refreshState();
}
}
>3.onStateChanged
每个tile状态变化都会走这里
public void onStateChanged(State s) {
boolean allReady = true;
for (TilePair pair: mQSTileList) {
if (!pair.mReady && pair.mTile.isTileReady()) {
pair.mTile.removeCallback(this);
pair.mTile.setListening(this, false);
pair.mReady = true;
} else if (!pair.mReady) {
allReady = false;
}
}
//所有tile都初始化完成
if (allReady) {
for (TilePair pair : mQSTileList) {
QSTile tile = pair.mTile;
final QSTile.State state = tile.getState().copy();
// Ignore the current state and get the generic label instead.
state.label = tile.getTileLabel();
tile.destroy();
//见7.5.3 数据加入集合
addTile(tile.getTileSpec(), null, state, true);
}
finished();
}
}
>4.finished
private void finished() {
//状态是false,因为自定义的还没处理
notifyTilesChanged(false);
//见6.3,这里是处理自定义的title,处理完会再次调用notifyTilesChanged,参数为true
addPackageTiles(mQSTileHost);
}
6.6.自定义的Tile
>1.CustomTile
有自定义的action: "android.service.quicksettings.action.QS_TILE_PREFERENCES",则跳转对应页面,没有就跳到应用的详情页
public Intent getLongClickIntent() {
Intent i = new Intent(TileService.ACTION_QS_TILE_PREFERENCES);
i.setPackage(mComponent.getPackageName());
i = resolveIntent(i);
if (i != null) {
i.putExtra(Intent.EXTRA_COMPONENT_NAME, mComponent);
i.putExtra(TileService.EXTRA_STATE, mTile.getState());
return i;
}
return new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
Uri.fromParts("package", mComponent.getPackageName(), null));
}
>2.TileLifecycleManager
- 默认是否是激活状态,必须有metadata,boolean类型的,key是"android.service.quicksettings.ACTIVE_TILE"
public boolean isActiveTile() {
try {
ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
META_DATA_QUERY_FLAGS);
return info.metaData != null
&& info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
- 是否状态可变,有metadata,key是"android.service.quicksettings.TOGGLEABLE_TILE"
public boolean isToggleableTile() {
try {
ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
META_DATA_QUERY_FLAGS);
return info.metaData != null
&& info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
7.TileAdapter.java
7.1.onTilesChanged
这个是设置所有支持的title数据,见6.4调用
public void onTilesChanged(List<TileInfo> tiles) {
mAllTiles = tiles;
recalcSpecs();
}
7.2.resetTileSpecs
重置默认的qs数据
public void resetTileSpecs(List<String> specs) {
//见3.8.4
mHost.changeTilesByUser(mCurrentSpecs, specs);
setTileSpecs(specs);
}
>1.setTileSpecs
currentSpecs是qs当前展示的tile
public void setTileSpecs(List<String> currentSpecs) {
if (currentSpecs.equals(mCurrentSpecs)) {
return;
}
mCurrentSpecs = currentSpecs;
recalcSpecs();
}
>2.recalcSpecs
根据对外展示的数据和总的数据,进行数据分类:
- mAllTiles是所有支持的titles
- mCurrentSpecs是对外要显示的titles
- 数据顺序,先加个null(到时候显示的是"Hold and drag to rearrange tiles" ),再添加对外显示的tiles
- 完事再加个null(到时候显示"Hold and drag to add tiles"),继续添加系统的tiles
- 完事继续加个null(到时候显示的内容是空,就是有个空白行),剩下的就是自定义的titles了
- 这里根据集合里添加null,把数据分成了3部分,最后那部分不一定有
private void recalcSpecs() {
if (mCurrentSpecs == null || mAllTiles == null) {
return;
}
//mOtherTiles先添加所有的数据,后边在把对外要显示的数据移除
mOtherTiles = new ArrayList<TileInfo>(mAllTiles);
mTiles.clear();
mTiles.add(null);
for (int i = 0; i < mCurrentSpecs.size(); i++) {
//从mOtherTiles里移除并添加到mTiles
final TileInfo tile = getAndRemoveOther(mCurrentSpecs.get(i));
if (tile != null) {
mTiles.add(tile);
}
}
mTiles.add(null);
for (int i = 0; i < mOtherTiles.size(); i++) {
final TileInfo tile = mOtherTiles.get(i);
//自定义的是false,其他的都是true,具体的逻辑在TileQueryHelper.java
if (tile.isSystem) {
mOtherTiles.remove(i--);
mTiles.add(tile);
}
}
mTileDividerIndex = mTiles.size();
mTiles.add(null);
//这时候other里剩下的只有自定义的tile了
mTiles.addAll(mOtherTiles);
updateDividerLocations();//补充3
notifyDataSetChanged();
}
简单举个例子来说明下:
- C1,C2假设是自定义的。
- mCurrentSpecs:当前显示的,加入数据是A ,B ,C1
- mAllTiles :包含所有的数据, A ,B ,C1, D,E,C2
- 最终mTiles里的数据是这样的, null,A ,B ,C1,null, D,E, null,C2
>3.updateDividerLocations
private void updateDividerLocations() {
mEditIndex = -1;
mTileDividerIndex = mTiles.size();
//从1开始
for (int i = 1; i < mTiles.size(); i++) {
if (mTiles.get(i) == null) {
if (mEditIndex == -1) {
mEditIndex = i; //这个对应上边例子里中间null的位置
} else {
mTileDividerIndex = i;//这个对应上边列子里最后那个null的位置
}
}
}
//如果没有C2的话,这里就直接刷新数据了。
if (mTiles.size() - 1 == mTileDividerIndex) {
notifyItemChanged(mTileDividerIndex);
}
}
7.3.saveSpecs
public void saveSpecs(QSTileHost host) {
List<String> newSpecs = new ArrayList<>();
clearAccessibilityState();
for (int i = 1; i < mTiles.size() && mTiles.get(i) != null; i++) {
newSpecs.add(mTiles.get(i).spec);
}
//最终保存到本地settings里
host.changeTilesByUser(mCurrentSpecs, newSpecs);
mCurrentSpecs = newSpecs;
}
7.4.隐藏某个tile
>1.QSTileHost.java
public QSTile createTile(String tileSpec) {
//比如要隐藏rotation,那么直接返回null
if ("rotation".equals(tileSpec)) {
return null;
}
for (int i = 0; i < mQsFactories.size(); i++) {
QSTile t = mQsFactories.get(i).createTile(tileSpec);
if (t != null) {
return t;
}
}
return null;
}
>2.isAvailable
- 直接找到对应的tile,重写下边的方法,这个方法默认是true,
- 而我们要添加tile的时候都会判断这个值的,如果不需要,我们把它改成false
- 比如RotationLockTile.java ,我们重写写上边的方法,改为false,那么也一样会被过滤掉
public boolean isAvailable() {
return true;
}
>3.getTiles
- 上边两种方法都是彻底隐藏某个tile,也就是说除了qs不显示,qs的编辑页面也不显示。
- 如果只是让qs页面不显示,也可以单纯的修改如下代码即可。
//QSTileHost.java
public Collection<QSTile> getTiles() {
//mTiles.remove("rotation);
return mTiles.values();
}
>4. QSPanelControllerBase
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
//..
for (QSTile tile : tiles) {
if ("rotation".equals(tile.getTileSpec())) {
//这里可以添加自己的逻辑,过滤不要的tile
continue;
}
addTile(tile, collapsedView);
}
}
8.TunerServiceImpl
- 小节3.8.1有提到过,数据来源是这个类。
- 这个类其实是给很多地方提供数据的,简单分析下。
@SysUISingleton
public class TunerServiceImpl extends TunerService {
private final ConcurrentHashMap<String, Set<Tunable>> mTunableLookup =
new ConcurrentHashMap<>();
8.0.TunerModule.java
- 注解生成实例对象
@Module
public interface TunerModule {
@Binds
TunerService provideTunerService(TunerServiceImpl controllerImpl);
}
8.1.构造方法
主要就是注册了user的切换
mCurrentUser = mUserTracker.getUserId();
mCurrentUserTracker = new UserTracker.Callback() {
@Override
public void onUserChanged(int newUser, Context userContext) {
mCurrentUser = newUser;
reloadAll();//补充1
reregisterAll();//补充2
}
};
mUserTracker.addCallback(mCurrentUserTracker,
new HandlerExecutor(mainHandler));
}
>1.reloadAll
重新加载数据并回调
private void reloadAll() {
for (String key : mTunableLookup.keySet()) {
String value = Settings.Secure.getStringForUser(mContentResolver, key,
mCurrentUser);
for (Tunable tunable : mTunableLookup.get(key)) {
tunable.onTuningChanged(key, value);
}
}
}
>2.reregisterAll
重新注册uri改变监听
protected void reregisterAll() {
if (mListeningUris.size() == 0) {
return;
}
mContentResolver.unregisterContentObserver(mObserver);
for (Uri uri : mListeningUris.keySet()) {
mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
}
}
8.2.addTunable
- 小节3.8构造方法里会调用,把QSTileHost加入
public void addTunable(Tunable tunable, String... keys) {
for (String key : keys) {
addTunable(tunable, key);
}
}
private void addTunable(Tunable tunable, String key) {
if (!mTunableLookup.containsKey(key)) {
mTunableLookup.put(key, new ArraySet<Tunable>());
}
mTunableLookup.get(key).add(tunable);
//...
Uri uri = Settings.Secure.getUriFor(key);
if (!mListeningUris.containsKey(uri)) {
mListeningUris.put(uri, key);
// 注册监听
mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
}
// Send the first state.
String value = DejankUtils.whitelistIpcs(() -> Settings.Secure
.getStringForUser(mContentResolver, key, mCurrentUser));
//拿到key对应的值,直接回调处理
tunable.onTuningChanged(key, value);
}
>1.Observer
uri发生变化的时候,会重新加载settings数据
private class Observer extends ContentObserver {
public void onChange(boolean selfChange, java.util.Collection<Uri> uris,
int flags, int userId) {
if (userId == mUserTracker.getUserId()) {
for (Uri u : uris) {
reloadSetting(u);
}
}
}
}
8.3.reloadSetting
private void reloadSetting(Uri uri) {
String key = mListeningUris.get(uri);
Set<Tunable> tunables = mTunableLookup.get(key);
if (tunables == null) {
return;
}
String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
for (Tunable tunable : tunables) {
//接口回调
tunable.onTuningChanged(key, value);
}
}
9.ServiceStateTracker.java
这里分析下小节2.2里combined_qs_header.xml布局里的qs_carrier.xml布局的内容如何显示
图片上T-Mobile文字的来源如下
9.1.getServiceProviderName
public String getServiceProviderName() {
// BrandOverride has higher priority than the carrier config
String operatorBrandOverride = getOperatorBrandOverride();
if (!TextUtils.isEmpty(operatorBrandOverride)) {
return operatorBrandOverride;
}
String carrierName = mIccRecords != null ? mIccRecords.getServiceProviderName() : "";
PersistableBundle config = getCarrierConfig();
if (config.getBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)
|| TextUtils.isEmpty(carrierName)) {
return config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING); //T-Mobile
}
return carrierName;
}
10.CarrierTextManager.java
字面意思,运行商文本信息管理器,下边简单整理下文本获取的过程.
显示的地方有两个,一个是锁屏界面,一个是飞行模式的情况下,下拉状态栏看到的,图中横线的部分。


相关的回调也有两个地方
10.1.CarrierTextController.java
>1.mCarrierTextCallback
这个是锁屏界面的回调
private final CarrierTextManager.CarrierTextCallback mCarrierTextCallback =
new CarrierTextManager.CarrierTextCallback() {
@Override
public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
mView.setText(info.carrierText);
}
@Override
public void startedGoingToSleep() {
mView.setSelected(false);
}
@Override
public void finishedWakingUp() {
mView.setSelected(true);
}
};
//设置监听
protected void onViewAttached() {
mCarrierTextManager.setListening(mCarrierTextCallback);
}
10.2.QSCarrierGroupController.java
>1.updateListeners
这个是飞行模式,下拉状态栏看到的那个
private void updateListeners() {
if (mListening) {
if (mNetworkController.hasVoiceCallingFeature()) {
mNetworkController.addCallback(mSignalCallback);
}
mCarrierTextManager.setListening(mCallback);
} else {
mNetworkController.removeCallback(mSignalCallback);
mCarrierTextManager.setListening(null);
}
}
回调
private static class Callback implements CarrierTextManager.CarrierTextCallback {
private H mHandler;
Callback(H handler) {
mHandler = handler;
}
@Override
public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
}
}
10.3.updateCarrierTextWithSimIoError
private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
final CharSequence carrier = "";
//根据sim卡状态返回不同的值
CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
// 循环检查所有的sim卡
for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
if (!mSimErrorState[index]) {//sim卡状态正常,啥也不干
continue;
}
//sim 卡有问题的时候的文本
//没有sim卡,sim是错误状态,就是说有坏卡
if (noSims) {
return concatenate(carrierTextForSimIOError,
getContext().getText(
com.android.internal.R.string.emergency_calls_only),
mSeparator);
} else if (subOrderBySlot[index] != -1) {
int subIndex = subOrderBySlot[index];
// 无效卡,当坏卡插在0或1的卡槽的时候
carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
carrierNames[subIndex],
mSeparator);
} else {
// concatenate "Invalid card" when faulty card is inserted in other slot
text = concatenate(text, carrierTextForSimIOError, mSeparator);
}
}
return text;
}
10.4.updateCarrierText
这个方法是在各个回调监听里调用的,具体的可以点击方法查看哪里调用了。
protected void updateCarrierText() {
boolean allSimsMissing = true;
boolean anySimReadyAndInService = false;
CharSequence displayText = null;
//这个返回的就是已有的sim卡的信息
List<SubscriptionInfo> subs = getSubscriptionInfo();
final int numSubs = subs.size();
final int[] subsIds = new int[numSubs];
final int[] subOrderBySlot = new int[mSimSlotsNumber];
for (int i = 0; i < mSimSlotsNumber; i++) {
subOrderBySlot[i] = -1;
}
final CharSequence[] carrierNames = new CharSequence[numSubs];
for (int i = 0; i < numSubs; i++) {
int subId = subs.get(i).getSubscriptionId();
carrierNames[i] = "";
subsIds[i] = subId;
subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
int simState = mKeyguardUpdateMonitor.getSimState(subId);
CharSequence carrierName = subs.get(i).getCarrierName();
//获取sim卡状态
CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
if (carrierTextForSimState != null) {
allSimsMissing = false;
carrierNames[i] = carrierTextForSimState;
}
if (simState == TelephonyManager.SIM_STATE_READY) {
ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
|| (mWifiManager != null && mWifiManager.isWifiEnabled()
&& mWifiManager.getConnectionInfo() != null
&& mWifiManager.getConnectionInfo().getBSSID() != null)) {
//有任何一个sim卡正常的话修改下边的字段
anySimReadyAndInService = true;
}
}
}
}
//没有sim卡并且sim卡都不在服务中
if (allSimsMissing && !anySimReadyAndInService) {
if (numSubs != 0) {
//没有sim卡,显示相关的文字
displayText = makeCarrierStringOnEmergencyCapable(
getMissingSimMessage(), subs.get(0).getCarrierName());
} else {
CharSequence text =
getContext().getText(com.android.internal.R.string.emergency_calls_only);
Intent i = getContext().registerReceiver(null,
new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
if (i != null) {
String spn = "";
String plmn = "";
if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
}
if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
}
//plmn和spn一样的话返回其中一个,不一样,合并下
if (Objects.equals(plmn, spn)) {
text = plmn;
} else {
text = concatenate(plmn, spn, mSeparator);
}
}
displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
}
}
//sim卡正常的话displayText就是空的,所以展示运行商名字
if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
allSimsMissing);
boolean airplaneMode = false;
//飞行模式的情况下显示的文本
if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
displayText = getAirplaneModeMessage();
airplaneMode = true;
}
//组合一个数据给其他回调使用
final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
"displayText",
carrierNames,
!allSimsMissing,
subsIds,
airplaneMode);
postToCallback(info);
}
11.总结
- 开头那2张图,一个显示4个setting,一个显示8个的,那其实是2种控件,前者叫quickQSPanel,后者叫QSPanel,拉倒底部通知栏消失的时候,那4个settings的容器quickQSPanel也同步隐藏了。
- 显示时间日期以及电量的状态栏,不是在这个qs_frame里处理的,是在NotificationShadeWindowView那边通过一个viewstub加载的motionLayout布局来 实现动画的
- 快速设置功能基本就在QSFragment里处理的,通过对应的Controller
- 简单整理下qs容器结构:QSPaneld(线性布局),xml里默认带一个footer布局。完事代码里添加了BrightnessView(亮度调节控件),以及PagedTileLayout(ViewPager),而ViewPager的页面就是TileLayout了(自定义的viewgroup)
- QSCustomizer,就是qs的编辑页面。
- 至于qs的默认数据来源,通过tunnerService获取的。
11.1.当前显示的qs数据
- 参考3.8,构造方法里调用小节8的addTunable方法,里边会读取settings.secure(sysui_qs_tiles)的值并调用回调
- 参考3.8.1,回调里会处理settings的值,参考3.8.5,如果settings的值为空,会读取默认的配置(默认配置值是default)
- 就会走到3.8.2,读取quick_settings_tiles_default
- 加载完成数据后会保存到settings,下次settings的就不是空了
11.2.qs编译页的数据
- 完整的数据参考6.1,quick_settings_tiles_stock
- 动态的是查找如下action的服务android.service.quicksettings.action.QS_TILE,还得有icon以及权限"android.permission.BIND_QUICK_SETTINGS_TILE"
>1.动态添加的qs服务
两种meta-data说明
- TOGGLEABLE_TILE 为true,表示这是个开关类型的,不写就是false
- ACTIVE_TILE 为true,表示这个默认是打开的,也就是亮的,不写就是false
<service
android:name=".service.MyQSService"
android:exported="true"
android:icon="@drawable/ic_indicator_flash_auto"
android:label="@string/display_settings"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<!-- <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE" android:value="true"/>-->
<!-- <meta-data android:name="android.service.quicksettings.ACTIVE_TILE" android:value="true"/>-->
</service>
>2.MyQSService
- 继承系统的TileService即可,已经封装好了对应的回调方法,如下,自己处理即可。
- getQsTile方法可以拿到Tile对象,自己可以修改图标,文字,状态。
open class MyQSService : TileService() {
var isConnected: AtomicBoolean = AtomicBoolean(false)
var isListening: AtomicBoolean = AtomicBoolean(false)
var hasBeenClicked: AtomicBoolean = AtomicBoolean(false)
open fun toggleState() {
if (isListening.get()) {
val tile: Tile = getQsTile()
when (tile.state) {
Tile.STATE_ACTIVE -> tile.state = Tile.STATE_INACTIVE
Tile.STATE_INACTIVE -> tile.state = Tile.STATE_ACTIVE
else -> {}
}
tile.updateTile()
}
}
override fun onClick() {
toggleState()//我这里啥也没干,就简单切换下状态
}
override fun onStartListening() {
isListening.set(true)
}
override fun onStopListening() {
isListening.set(false)
}
override fun onTileAdded() {
isConnected.set(true)
}
override fun onTileRemoved() {
isConnected.set(false)
}
11.4.获取修改显示的qs
获取当前显示的qs
$adb shell settings get secure sysui_qs_tiles
internet,bt,dnd,flashlight,rotation,battery,airplane,night,screenrecord,custom(com.google.android.gms/.nearby.sharing.SharingTileService),dbg:mem
修改qs,带括号的得加反斜杠转义,得带引号,要不转义不识别,否则报错
> adb shell settings put secure sysui_qs_tiles
"internet,bt,dnd,flashlight,rotation,battery,airplane,night,screenrecord,custom\(com.google.android.gms/.nearby.sharing.SharingTileService\),dbg:mem"