android framework13-status bar[05 quick settings]

2,232 阅读28分钟

1.简介

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

image.png

image.png

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按钮 image.png

<!-- 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

红色部分就是

image.png

展开状态

image.png 需要注意的是,下边布局里的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 image.png
    <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

就是快速设置容器完全可见的时候,底部的功能,包括设置,电源,头像等,如图

image.png

    <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

image.png

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右下角那个编辑按钮跳转的页面。

image.png

    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

字面意思,运行商文本信息管理器,下边简单整理下文本获取的过程.

显示的地方有两个,一个是锁屏界面,一个是飞行模式的情况下,下拉状态栏看到的,图中横线的部分。

image.png

image.png

相关的回调也有两个地方

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"