android framework13-SystemUi(02 CollapsedStatusBarFragment)

1,760 阅读19分钟

1.简介

这篇主要学习下非展开的状态栏,也就是收缩状态栏,下图红线部分

image.png

2.视图加载流程

复习下StatusBarWindowView如何添加

  • 首先CentralSurfacesImpl.java的构造方法里实例化了StatusBarWindowController.java,
  • 而StatusBarWindowController的构造方法里又实例化了StatusBarWindowView,
  • 完事attach方法addView

>1.CentralSurfacesImpl.java

CentralSurfacesImpl里边会调用StatusBarWindowController的attach方法

        @Inject
        public CentralSurfacesImpl(
    //...
                StatusBarWindowController statusBarWindowController,//controller
        //...
        public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
            makeStatusBarView(result);
            mNotificationShadeWindowController.attach();
            mStatusBarWindowController.attach();//见2.1.1添加到窗口
        }

2.1.StatusBarWindowController

    @Inject
    public StatusBarWindowController(
            Context context,
            //构造方法传递的view
            @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView,
 //...           

>1.attach

        public void attach() {
        //参数见补充2
           mLp = getBarLayoutParams(mContext.getDisplay().getRotation());
           // 添加view
           mWindowManager.addView(mStatusBarWindowView, mLp);

>2.getBarLayoutParams

如下,状态栏高度是固定的

    private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
        int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rotation);
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,
                height,

2.2.问题?

需求是隐藏状态栏

  • 测试1 :我想着把上边的height改成0就完事了,结果改完测试发现不行。 height为0以后状态栏还在,测试失败。why?

  • 测试2 :调用mWindowManager.removeView(mStatusBarWindowView),状态栏确实不见了,可是锁屏再解锁屏幕,程序崩掉了,测试失败。

  • 测试3 :调用mStatusBarWindowView.setVisibility(View.GONE); 这个好像可以,不过home页面或者all apps页面,其实可以看到顶部还是有一个状态栏高度的空白的【这个应该是launcher3里边弄的padding】,不过打开一个app的话,发现顶部没有空白,基本ok

2.3.StatusBarWindowView

>1.注解生成的

@Module
abstract class StatusBarWindowModule {
    @Module
    companion object {
        @JvmStatic
        @Provides
        @SysUISingleton
        @InternalWindowView
        fun providesStatusBarWindowView(layoutInflater: LayoutInflater): StatusBarWindowView {
            return layoutInflater.inflate(
                R.layout.super_status_bar,
                /* root= */null
            ) as StatusBarWindowView?

}

>2.布局super_status_bar

<!-- This is the status bar window. -->
<com.android.systemui.statusbar.window.StatusBarWindowView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <FrameLayout
        android:id="@+id/status_bar_launch_animation_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <FrameLayout
        android:id="@+id/status_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/system_bar_background" />
</com.android.systemui.statusbar.window.StatusBarWindowView>

2.4.@+id/status_bar_container

  • 之后那个id为status_bar_container的布局会被替换成一个Fragment,
  • 具体逻辑在上节5.1的下边这行代码
    ## CentralSurfacesImpl.java
            // Set up CollapsedStatusBarFragment and PhoneStatusBarView
            StatusBarInitializer initializer = mCentralSurfacesComponent.getStatusBarInitializer();
    //...见补充1
            initializer.initializeStatusBar(mCentralSurfacesComponent);

>1.StatusBarInitializer

StatusBarInitializer.kt通过replace方法,把布局里的容器换成了Fragment

    fun initializeStatusBar(
        centralSurfacesComponent: CentralSurfacesComponent
    ) {
        windowController.fragmentHostManager.addTagListener(
                CollapsedStatusBarFragment.TAG,
                object : FragmentHostManager.FragmentListener {
                    override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
                        val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment)
                                .statusBarFragmentComponent ?: throw IllegalStateException()
                        statusBarViewUpdatedListener?.onStatusBarViewUpdated(
                            statusBarFragmentComponent.phoneStatusBarView,//注解获取的
                            statusBarFragmentComponent.phoneStatusBarViewController,
                            statusBarFragmentComponent.phoneStatusBarTransitions
                        )
                        creationListeners.forEach { listener ->
                            listener.onStatusBarViewInitialized(statusBarFragmentComponent)
                        }
                    }

                    override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
                        // nop
                    }
                }).fragmentManager
                .beginTransaction()
                .replace(R.id.status_bar_container,//这里
                        centralSurfacesComponent.createCollapsedStatusBarFragment(),//替换的fragment
                        CollapsedStatusBarFragment.TAG)
                .commit()
    }
  • centralSurfacesComponent.createCollapsedStatusBarFragment() 这个注解生成的fragment就是下边要讲的

3.CollapsedStatusBarFragment

3.1.onCreateView

    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.status_bar, container, false);
    }

>1.status_bar.xml

## 帧布局
<com.android.systemui.statusbar.phone.PhoneStatusBarView
    android:layout_width="match_parent"
    android:layout_height="@dimen/status_bar_height"
    android:id="@+id/status_bar"
    android:orientation="vertical"
    android:focusable="false"
    android:descendantFocusability="afterDescendants"
    android:accessibilityPaneTitle="@string/status_bar"
    >

    <ImageView
        android:id="@+id/notification_lights_out"
        android:layout_width="@dimen/status_bar_icon_size"
        android:layout_height="match_parent"
        android:paddingStart="@dimen/status_bar_padding_start"
        android:paddingBottom="2dip"
        android:src="@drawable/ic_sysbar_lights_out_dot_small"
        android:scaleType="center"
        android:visibility="gone"
        />

    <LinearLayout android:id="@+id/status_bar_contents"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingStart="@dimen/status_bar_padding_start"
        android:paddingEnd="@dimen/status_bar_padding_end"
        android:paddingTop="@dimen/status_bar_padding_top"
        android:orientation="horizontal">

        <!-- Container for the entire start half of the status bar. It will always use the same
             width, independent of the number of visible children and sub-children.
             child分析见补充4-->
        <FrameLayout
            android:id="@+id/status_bar_start_side_container"
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:clipChildren="false"
            android:layout_weight="1">

            <!-- Container that is wrapped around the views on the start half of the status bar.
                 Its width will change with the number of visible children and sub-children.
                 It is useful when we want to know the visible bounds of the content. -->
            <FrameLayout
                android:id="@+id/status_bar_start_side_content"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:clipChildren="false">
               
                <include layout="@layout/heads_up_status_bar_layout" />

                <!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the
                     individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK
                     and DISABLE_NOTIFICATION_ICONS, respectively -->
                <LinearLayout
                    android:id="@+id/status_bar_start_side_except_heads_up"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent"
                    android:clipChildren="false">
                     <!--补充7-->
                    <ViewStub
                        android:id="@+id/operator_name"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:layout="@layout/operator_name" />

                    <com.android.systemui.statusbar.policy.Clock
                        android:id="@+id/clock"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                        android:singleLine="true"
                        android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
                        android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
                        android:gravity="center_vertical|start"
                    />

                    <include layout="@layout/ongoing_call_chip" />

                    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                        android:id="@+id/notification_icon_area"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:orientation="horizontal"
                        android:clipChildren="false"/>

                </LinearLayout>
            </FrameLayout>
        </FrameLayout>

        <!-- Space should cover the notch (if it exists) and let other views lay out around it -->
        <android.widget.Space
            android:id="@+id/cutout_space_view"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:gravity="center_horizontal|center_vertical"
        />

        <!-- Container for the entire end half of the status bar. It will always use the same
             width, independent of the number of visible children and sub-children. 
             child分析见补充5-->
        <FrameLayout
            android:id="@+id/status_bar_end_side_container"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:clipChildren="false">

            <!-- Container that is wrapped around the views on the end half of the
                 status bar. Its width will change with the number of visible children and
                 sub-children.
                 It is useful when we want know the visible bounds of the content.-->
            <com.android.keyguard.AlphaOptimizedLinearLayout
                android:id="@+id/status_bar_end_side_content"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_gravity="end"
                android:orientation="horizontal"
                android:gravity="center_vertical|end"
                android:clipChildren="false">

                <include
                    android:id="@+id/user_switcher_container"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
                    layout="@layout/status_bar_user_chip_container" />

                <include layout="@layout/system_icons" />
            </com.android.keyguard.AlphaOptimizedLinearLayout>
        </FrameLayout>
    </LinearLayout>

    <ViewStub
        android:id="@+id/emergency_cryptkeeper_text"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout="@layout/emergency_cryptkeeper_text"
    />

</com.android.systemui.statusbar.phone.PhoneStatusBarView>

>2.ongoing_call_chip.xml

<FrameLayout
    android:id="@+id/ongoing_call_chip"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical|start"
    android:layout_marginStart="5dp"
>
    <LinearLayout
        android:id="@+id/ongoing_call_chip_background"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/ongoing_appops_chip_height"
        android:layout_gravity="center_vertical"
        android:gravity="center"
        android:background="@drawable/ongoing_call_chip_bg"
        android:paddingStart="@dimen/ongoing_call_chip_side_padding"
        android:paddingEnd="@dimen/ongoing_call_chip_side_padding"
        android:contentDescription="@string/ongoing_phone_call_content_description"
        android:minWidth="@dimen/min_clickable_item_size"
    >

        <ImageView
            android:src="@*android:drawable/ic_phone"
            android:layout_width="@dimen/ongoing_call_chip_icon_size"
            android:layout_height="@dimen/ongoing_call_chip_icon_size"
            android:tint="?android:attr/colorPrimary"
        />

        <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer
            android:id="@+id/ongoing_call_chip_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:gravity="center|start"
            android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding"
            android:textAppearance="@android:style/TextAppearance.Material.Small"
            android:fontFamily="@*android:string/config_headlineFontFamily"
            android:textColor="?android:attr/colorPrimary"
        />

    </LinearLayout>
</FrameLayout>

>3.简化status_bar.xml

  • 主要看下核心的第二层,里边有2个帧布局,比重是都是1
    <com.android.systemui.statusbar.phone.PhoneStatusBarView
        >
    ## 第一层
        <ImageView
            android:id="@+id/notification_lights_out"
            />
    ## 第二层
        <LinearLayout android:id="@+id/status_bar_contents"
            <!--内容分析见补充4-->
            <FrameLayout
                android:id="@+id/status_bar_start_side_container"

            <android.widget.Space
                android:id="@+id/cutout_space_view"
            />
        <!--内容分析见补充5-->
            <FrameLayout
                android:id="@+id/status_bar_end_side_container"
            </FrameLayout>
        </LinearLayout>
    ## 第三层
        <ViewStub
            android:layout="@layout/emergency_cryptkeeper_text"
        />

    </com.android.systemui.statusbar.phone.PhoneStatusBarView>

下边就具体来看下第二层布局里,左右两部分ui都有啥

>4.status_bar_start_side_container

状态栏左侧部分

<LinearLayout android:id="@+id/status_bar_start_side_except_heads_up" > 
# sim卡运营商的名字,调用的getCarrierName方法,fragment里初始化的
<ViewStub android:id="@+id/operator_name" 
android:layout="@layout/operator_name" /> 
# 当前时间
<com.android.systemui.statusbar.policy.Clock 
android:id="@+id/clock"  /> 
# 通话时长
<include layout="@layout/ongoing_call_chip" /> 
# 其他通知icon
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"/> 
</LinearLayout>

>5.status_bar_end_side_container

状态栏右侧部分

        <FrameLayout
            android:id="@+id/status_bar_end_side_container">

            <com.android.keyguard.AlphaOptimizedLinearLayout
                android:id="@+id/status_bar_end_side_content">

# 当前用户的信息,头像和名字
                <include
                    android:id="@+id/user_switcher_container"
                    layout="@layout/status_bar_user_chip_container" />
#系统icons,见补充6
                <include layout="@layout/system_icons" />
            </com.android.keyguard.AlphaOptimizedLinearLayout>
        </FrameLayout>

>6.system_icons.xml

可以看到,右侧固定有个电池图标

<LinearLayout 
    android:layout_gravity="center_vertical|end"
    android:gravity="center_vertical">
## 这里是动态添加的icon
    <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:paddingEnd="@dimen/signal_cluster_battery_padding"
        android:gravity="center_vertical"
        android:orientation="horizontal"/>
## 固定有个电池控件
    <com.android.systemui.battery.BatteryMeterView android:id="@+id/battery" />
</LinearLayout>

>7.operator_name.xml

服务商的名字或者通讯公司的名字?

<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/operator_name_frame"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   >
   <!---就是个textview-->
   <com.android.systemui.statusbar.OperatorNameView
       android:id="@+id/operator_name"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:maxLength="20"
       android:gravity="center_vertical|start"
       android:textAppearance="?android:attr/textAppearanceSmall"
       android:singleLine="true"
       android:paddingEnd="5dp" />
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>

3.2.onViewCreated

    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
//...
        mStatusBarFragmentComponent.init();
//...
        mStatusBar = (PhoneStatusBarView) view;
//...
//statusIcons就是右侧的icon容器,电量图标左侧的部分,见5.2
        mDarkIconManager = mDarkIconManagerFactory.create(
                view.findViewById(R.id.statusIcons), StatusBarLocation.HOME);
//...
    //就是默认不显示的icon,从配置里读取,然后赋值给IconManager,显示的时候会判断过滤掉
        updateBlockedIcons();//补充1
        mStatusBarIconController.addIconGroup(mDarkIconManager);//具体逻辑见下边详情
//...
        initNotificationIconArea();//状态栏左侧时间控件右边的通知图标容器
//...
    }

>1.updateBlockedIcons

更新不显示的图标集合

    void updateBlockedIcons() {
        mBlockedIcons.clear();

        //数据见补充2
        List<String> blockList = Arrays.asList(getResources().getStringArray(
                R.array.config_collapsed_statusbar_icon_blocklist));
         //震动图标名字
        String vibrateIconSlot = getString(com.android.internal.R.string.status_bar_volume);
        //状态栏是否显示震动图标,
        boolean showVibrateIcon =
                mSecureSettings.getIntForUser(
                        Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
                        0,
                        UserHandle.USER_CURRENT) == 0;

        // Filter out vibrate icon from the blocklist if the setting is on
        for (int i = 0; i < blockList.size(); i++) {
            if (blockList.get(i).equals(vibrateIconSlot)) {
                if (showVibrateIcon) {
                    mBlockedIcons.add(blockList.get(i));
                }
            } else {
                mBlockedIcons.add(blockList.get(i));
            }
        }
        //见5.3.1把数据给IconManager
        mMainExecutor.execute(() -> mDarkIconManager.setBlockList(mBlockedIcons));
    }

>2.config_collapsed_statusbar_icon_blocklist

哪些图标不需要显示在收缩状态栏的,可以在这里配置

    <!-- Icons that don't show in a collapsed non-keyguard statusbar -->
    <string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
        <item>@*android:string/status_bar_volume</item>
        <item>@*android:string/status_bar_alarm_clock</item>
        <item>@*android:string/status_bar_call_strength</item>
    </string-array>

>3.initNotificationIconArea

    public void initNotificationIconArea() {
    //通知图标容器
        ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
        //通知图标是这个controller里处理的
        mNotificationIconAreaInner =
                mNotificationIconAreaController.getNotificationInnerAreaView();
           //移除旧的
        if (mNotificationIconAreaInner.getParent() != null) {
            ((ViewGroup) mNotificationIconAreaInner.getParent())
                    .removeView(mNotificationIconAreaInner);
        }
        //加入容器
        notificationIconArea.addView(mNotificationIconAreaInner);

        //根据disable状态更新通知图标的显示与否,以及来电通知图标。
        updateNotificationIconAreaAndCallChip(mDisabled1, false);
    }

3.3.StatusBarIconControllerImpl.java

>1.addIconGroup方法

    public void addIconGroup(IconManager group) {
//...
        group.setController(this);
        mIconGroups.add(group);
        List<Slot> allSlots = mStatusBarIconList.getSlots();
        for (int i = 0; i < allSlots.size(); i++) {
            Slot slot = allSlots.get(i);
            List<StatusBarIconHolder> holders = slot.getHolderListInViewOrder();
            boolean hidden = mIconHideList.contains(slot.getName());
//这里刚开始holders集合是空的,所以啥也没干
            for (StatusBarIconHolder holder : holders) {
                int viewIndex = mStatusBarIconList.getViewIndex(slot.getName(), holder.getTag());
                group.onIconAdded(viewIndex, slot.getName(), hidden, holder);
            }
        }
    }

3.4..StatusBarIconList

这个对象是注解生成的,数据是配置里读取的,相关的数组在frameworks/base/core/res/res/values/config.xml 数组长度有30多个,就不贴了。

>1.注解生成对象

    @Provides
    @SysUISingleton
    static StatusBarIconList provideStatusBarIconList(Context context) {
        return new StatusBarIconList(
                context.getResources().getStringArray(
                        com.android.internal.R.array.config_statusBarIcons));
    }

>2.构造方法

public class StatusBarIconList {
    private final ArrayList<Slot> mSlots = new ArrayList<>();
    private final List<Slot> mViewOnlySlots = Collections.unmodifiableList(mSlots);

    public StatusBarIconList(String[] slots) {
        final int N = slots.length;
        for (int i = 0; i < N; i++) {
            mSlots.add(new Slot(slots[i], null));//就个名字
        }
    }

    /** Returns the list of current slots. */
    public List<Slot> getSlots() {
        return mViewOnlySlots;
    }

>3.getHolderListInViewOrder

可以看到,默认的Slot就名字不是空的,其他都是空的,所以下边if条件都不满足。

        public List<StatusBarIconHolder> getHolderListInViewOrder() {
            ArrayList<StatusBarIconHolder> holders = new ArrayList<>();
            if (mSubSlots != null) {
                for (int i = mSubSlots.size() - 1; i >= 0; i--) {
                    holders.add(mSubSlots.get(i));
                }
            }

            if (mHolder != null) {
                holders.add(mHolder);
            }

            return holders;
        }

3.5.PhoneStatusBarView

这个类主要计算下status bar的高度,监听下clock,battery图标的黑暗颜色变化,还有cutout_space_view的大小动态设置

    public class PhoneStatusBarView extends FrameLayout {

4.NotificationIconAreaController.java

4.1.initializeNotificationAreaViews

构造方法里调用

    protected void initializeNotificationAreaViews(Context context) {
        reloadDimens(context);

        LayoutInflater layoutInflater = LayoutInflater.from(context);
        mNotificationIconArea = inflateIconArea(layoutInflater);
        mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);

    }

>1.inflateIconArea

    protected View inflateIconArea(LayoutInflater inflater) {
        return inflater.inflate(R.layout.notification_icon_area, null);
    }

>2.notification_icon_area.xml

<com.android.keyguard.AlphaOptimizedLinearLayout
    android:id="@+id/notification_icon_area_inner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false">
    <!--自定义的ViewGroup-->
    <com.android.systemui.statusbar.phone.NotificationIconContainer
        android:id="@+id/notificationIcons"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:clipChildren="false"/>
</com.android.keyguard.AlphaOptimizedLinearLayout>

4.2.updateNotificationIcons

  • 外部调用,有新的通知的时候,测试了一下,新加一条通知,这个方法可能调用好几次,集合数据个数也可能变化好几次。
  • 这就牵扯到数据来源了,它和下拉看到的通知栏效果相关的,比如新加一条通知,会显示在下拉通知栏,完事可能会合并成一个,这时候通知数据也变化了。
    public void updateNotificationIcons(List<ListEntry> entries) {
        mNotificationEntries = entries;
        updateNotificationIcons();
    }
    //更新不同的通知,容器不一样,走的都是4.3的方法
    private void updateNotificationIcons() {
        updateStatusBarIcons();//补充1
        updateShelfIcons();//就是下拉通知栏,通知太多显示不下,底部会显示看不到的通知图标。
        updateAodNotificationIcons();//这个是锁屏界面时钟下边的,不一定会显示,容器可能是gone

        applyNotificationIconsTint();//给icon染色,
    }    

>1.updateStatusBarIcons

    public void updateStatusBarIcons() {
        updateIconsForLayout(entry -> entry.getIcons().getStatusBarIcon(), mNotificationIcons,
                false /* showAmbient */,
                mShowLowPriority,
                true /* hideDismissed */,
                true /* hideRepliedMessages */,
                false /* hideCurrentMedia */,
                false /* hidePulsing */);
    }

4.3.updateIconsForLayout

    private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
            NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
            boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
            boolean hidePulsing) {
        ArrayList<StatusBarIconView> toShow = new ArrayList<>(mNotificationEntries.size());
        // 过滤掉媒体通知以及子通知
        for (int i = 0; i < mNotificationEntries.size(); i++) {
            NotificationEntry entry = mNotificationEntries.get(i).getRepresentativeEntry();
            if (entry != null && entry.getRow() != null) {
            //这里判断过滤掉不需要显示的通知
                if (shouldShowNotificationIcon(entry, showAmbient, showLowPriority, hideDismissed,
                        hideRepliedMessages, hideCurrentMedia, hidePulsing)) {
                    //加入集合
                    StatusBarIconView iconView = function.apply(entry);
                    if (iconView != null) {
                        toShow.add(iconView);
                    }
                }
            }
        }

        ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
        ArrayList<View> toRemove = new ArrayList<>();
        //循环容器里已有的通知图标
        for (int i = 0; i < hostLayout.getChildCount(); i++) {
            View child = hostLayout.getChildAt(i);
            if (!(child instanceof StatusBarIconView)) {
                continue;
            }
            //child不在要显示的通知集合里,也就是需要移除的
            if (!toShow.contains(child)) {
                //默认false
                boolean iconWasReplaced = false;
                StatusBarIconView removedIcon = (StatusBarIconView) child;
                //要移除的图标groupKey
                String removedGroupKey = removedIcon.getNotification().getGroupKey();
                for (int j = 0; j < toShow.size(); j++) {
                    StatusBarIconView candidate = toShow.get(j);
                    //要移除的child和要显示的比较:icon一样,groupkey一样
                    if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
                            && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
                        //修改标志,取反。
                        //有单数个一样的,replace为true,有偶数个一样的,replace还是false
                        if (!iconWasReplaced) {
                            iconWasReplaced = true;
                        } else {
                            iconWasReplaced = false;
                            break;
                        }
                    }
                }
                //最终如果需要替换的话
                if (iconWasReplaced) {
                //放入要替换的map里,key是groupKey
                    ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
                    if (statusBarIcons == null) {
                        statusBarIcons = new ArrayList<>();
                        replacingIcons.put(removedGroupKey, statusBarIcons);
                    }
                    statusBarIcons.add(removedIcon.getStatusBarIcon());
                }
                //加入集合
                toRemove.add(removedIcon);
            }
        }
        //移除所有重复的
        ArrayList<String> duplicates = new ArrayList<>();
        for (String key : replacingIcons.keySet()) {
            ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
            //不为1,说明同一个groupKey下有多个通知
            if (statusBarIcons.size() != 1) {
                duplicates.add(key);
            }
        }
        replacingIcons.removeAll(duplicates);//把重复的从map里删除
        hostLayout.setReplacingIcons(replacingIcons);//给容器设置新的数据

        final int toRemoveCount = toRemove.size();
        for (int i = 0; i < toRemoveCount; i++) {
            hostLayout.removeView(toRemove.get(i));//容器移除需要删除的view
        }

        final FrameLayout.LayoutParams params = generateIconLayoutParams();
        for (int i = 0; i < toShow.size(); i++) {
            StatusBarIconView v = toShow.get(i);
            // The view might still be transiently added if it was just removed and added again
            hostLayout.removeTransientView(v);
            if (v.getParent() == null) {
                if (hideDismissed) {
                    v.setOnDismissListener(mUpdateStatusBarIcons);//监听回调就是4.2.1
                }
                hostLayout.addView(v, i, params);//添加新的
            }
        }

        hostLayout.setChangingViewPositions(true);
        //按照通知的顺序重新排序child
        final int childCount = hostLayout.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View actual = hostLayout.getChildAt(i);
            StatusBarIconView expected = toShow.get(i);
            if (actual == expected) {
                continue;
            }
            hostLayout.removeView(expected);
            hostLayout.addView(expected, i);
        }
        hostLayout.setChangingViewPositions(false);
        hostLayout.setReplacingIcons(null);//置空数据
    }

5.DarkIconManager

继承IconManager,主要处理icon的动态添加逻辑

5.1.构造方法

            public DarkIconManager(
                    LinearLayout linearLayout,
                    StatusBarLocation location,
    //...
                    DarkIconDispatcher darkIconDispatcher) {
                super(linearLayout,
                        location,

5.2.工厂模式Factory.create

create方法第一个参数,就是icon要添加到的viewGroup

            public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
                return new DarkIconManager(
                        group,
                        location,

5.3.IconManager

        public IconManager(
                ViewGroup group,
                StatusBarLocation location,
                //...
        ) {
            mGroup = group;
            mStatusBarPipelineFlags = statusBarPipelineFlags;
            

>1. setBlockList

设置需要隐藏的图标集合

            public void setBlockList(@Nullable List<String> blockList) {
                mBlockList.clear();
                mBlockList.addAll(blockList);
                if (mController != null) {
                    mController.refreshIconGroup(this);
                }
            }  

>2.onIconAdded

        protected void onIconAdded(int index, String slot, boolean blocked,
                StatusBarIconHolder holder) {
            addHolder(index, slot, blocked, holder);
        }

>3.addHolder

  • 可以看到blocked字段对wifi和mobile图标是不生效的。
            protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
                    StatusBarIconHolder holder) {
                // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
                // that is in our blocked list, then we'll never see it
                if (mBlockList.contains(slot)) {
                //需要隐藏
                    blocked = true;
                }
                //下边add方法就是把view添加到mGroup里
                switch (holder.getType()) {
                    case TYPE_ICON:
                        return addIcon(index, slot, blocked, holder.getIcon());

                    case TYPE_WIFI:
                        return addWifiIcon(index, slot, holder.getWifiState());

                    case TYPE_WIFI_NEW:
                        return addNewWifiIcon(index, slot);

                    case TYPE_MOBILE:
                        return addMobileIcon(index, slot, holder.getMobileState());

                    case TYPE_MOBILE_NEW:
                        return addNewMobileIcon(index, slot, holder.getTag());
                }

                return null;
            }        

找不到icon哪里添加的,那从最基本的来找,就是前边的贴的IconManager里有addView的方法,所以查看哪里调用即可。我们Fragment里用到的是DarkIconManager,查下里边的onIconAdded方法都哪里用到,很容易找到StatusBarIconControllerImpl.java里有用到,这个类里有很多setIcon的方法,找下哪里用的, 最后发现icon都在下边2个XXXPolicy类里添加的。

6.PhoneStatusBarPolicy(普通图标)

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java

6.1.初始化

这个类是CentralSurfacesImpl的构造方法里生成的,然后在其start方法里init的,如下

//上一篇说过的,这个start会自动执行的
    public void start() {
    //...
       // Lastly, call to the icon policy to install/update all the icons.
        mIconPolicy.init();

CentralSurfacesImpl是实现接口CoreStartable的,会自动执行start方法

public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {

>1.init方法

  • 可以看到里边添加的icon都默认不可见的,这里应该就是初始化下icon数据,
  • 方法最后是一些监听回调,会动态修改图标的可见性。
        // TTY status: teletypewriter,电传打字机的缩写,指的是一种用于文本通信的设备
        updateTTY();

        // bluetooth status ,看了下逻辑,只有在蓝牙连接并且audio正常的情况下才有这个图标
        updateBluetooth();

        // Alarm clock
        mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
        mIconController.setIconVisibility(mSlotAlarmClock, false);

        // zen
        mIconController.setIcon(mSlotZen, R.drawable.stat_sys_dnd, null);
        mIconController.setIconVisibility(mSlotZen, false);

        // vibrate
        mIconController.setIcon(mSlotVibrate, R.drawable.stat_sys_ringer_vibrate,
                mResources.getString(R.string.accessibility_ringer_vibrate));
        mIconController.setIconVisibility(mSlotVibrate, false);
        // mute
        mIconController.setIcon(mSlotMute, R.drawable.stat_sys_ringer_silent,
                mResources.getString(R.string.accessibility_ringer_silent));
        mIconController.setIconVisibility(mSlotMute, false);
        updateVolumeZen();

        // cast
        mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
        mIconController.setIconVisibility(mSlotCast, false);

        // hotspot
        mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
                mResources.getString(R.string.accessibility_status_bar_hotspot));
        mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());

        // managed profile
        updateManagedProfile();

        // data saver
        mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
                mResources.getString(R.string.accessibility_data_saver_on));
        mIconController.setIconVisibility(mSlotDataSaver, false);


        // privacy items
        String microphoneString = mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId());
        String microphoneDesc = mResources.getString(
                R.string.ongoing_privacy_chip_content_multiple_apps, microphoneString);
        mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(),
                microphoneDesc);
        mIconController.setIconVisibility(mSlotMicrophone, false);

        String cameraString = mResources.getString(PrivacyType.TYPE_CAMERA.getNameId());
        String cameraDesc = mResources.getString(
                R.string.ongoing_privacy_chip_content_multiple_apps, cameraString);
        mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(),
                cameraDesc);
        mIconController.setIconVisibility(mSlotCamera, false);

        mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
                mResources.getString(R.string.accessibility_location_active));
        mIconController.setIconVisibility(mSlotLocation, false);

        // sensors off
        mIconController.setIcon(mSlotSensorsOff, R.drawable.stat_sys_sensors_off,
                mResources.getString(R.string.accessibility_sensors_off_active));
        mIconController.setIconVisibility(mSlotSensorsOff,
                mSensorPrivacyController.isSensorPrivacyEnabled());

        // screen record
        mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, null);
        mIconController.setIconVisibility(mSlotScreenRecord, false);

//下边这些回调,会动态修改图标的可见性
        mRotationLockController.addCallback(this);
        mBluetooth.addCallback(this);
        mProvisionedController.addCallback(this);
        mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
        mZenController.addCallback(this);
        mCast.addCallback(mCastCallback);
        mHotspot.addCallback(mHotspotCallback);
        mNextAlarmController.addCallback(mNextAlarmCallback);
        mDataSaver.addCallback(this);
        mKeyguardStateController.addCallback(this);
        mPrivacyItemController.addCallback(this);
        mSensorPrivacyController.addCallback(mSensorPrivacyListener);
        mLocationController.addCallback(this);
        mRecordingController.addCallback(this);

        mCommandQueue.addCallback(this);
    }

7.StatusBarSignalPolicy(信号图标)

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java

7.1.初始化

同上,也是CentralSurfacesImpl.java构造方法里实例化对象,start方法里执行init方法

    public void start() {
//...
        mStatusBarSignalPolicy.init();

>1.init方法

在监听里处理icon的

    public void init() {
        if (mInitialized) {
            return;
        }
        mInitialized = true;
        mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
        mNetworkController.addCallback(this);
        mSecurityController.addCallback(this);
    }

看下这个类用到的字符串,就知道他都处理哪些图标了

        mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
        mSlotMobile   = mContext.getString(com.android.internal.R.string.status_bar_mobile);
        mSlotWifi     = mContext.getString(com.android.internal.R.string.status_bar_wifi);
        mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet);
        mSlotVpn      = mContext.getString(com.android.internal.R.string.status_bar_vpn);
        mSlotNoCalling = mContext.getString(com.android.internal.R.string.status_bar_no_calling);
        mSlotCallStrength =
                mContext.getString(com.android.internal.R.string.status_bar_call_strength);
        mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);

8.StatusBarIconControllerImpl

public class StatusBarIconControllerImpl implements Tunable,
        ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {

8.1.setIcon

很多setIcon的方法,如下图

image.png 这里就拿其中一种来分析下流程,默认的holder是null的,所以下边的代码会进入if,创建新的holder

    public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
        StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
        if (holder == null) {
        //实例化一个icon对象
            StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
                    Icon.createWithResource(
                            mContext, resourceId), 0, 0, contentDescription);
                            //创建holder对象,并把icon赋值给holder对象
            holder = StatusBarIconHolder.fromIcon(icon);
            setIcon(slot, holder);
        } else {
        //更新下icon信息即可
            holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
            holder.getIcon().contentDescription = contentDescription;
            handleSet(slot, holder);
        }
    }

>1.StatusBarIconHolder.fromIcon(icon)

   public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
        StatusBarIconHolder wrapper = new StatusBarIconHolder();
        wrapper.mIcon = icon;
        return wrapper;
    }

>2.setIcon

前边分析过mStatusBarIconList时注解生成的,默认里边的holder是null的,所以这里判断下,为null就addIcon,不为null就做更新操作

    private void setIcon(String slot, @NonNull StatusBarIconHolder holder) {
    
        boolean isNew = mStatusBarIconList.getIconHolder(slot, holder.getTag()) == null;
        mStatusBarIconList.setIcon(slot, holder);//给对应的icon设置holder

        if (isNew) {
            addSystemIcon(slot, holder);
        } else {
            handleSet(slot, holder);
        }
    }

继续

    private void addSystemIcon(String slot, StatusBarIconHolder holder) {
        int viewIndex = mStatusBarIconList.getViewIndex(slot, holder.getTag());
        boolean hidden = mIconHideList.contains(slot);
//这个是添加新的icon  view,mIconGroups里目前已知的是那个 mDarkIconManager
        mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
    }

>3.handleSet

    private void handleSet(String slotName, StatusBarIconHolder holder) {
        int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
        //这里是找到就的icon view,更新其显示的图标
        mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
    }

8.2.Icon的种类

主要就普通的icon和特殊的icon(wifi,mobile)

>1.普通的icon

        protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
                StatusBarIcon icon) {
            StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
            view.set(icon);
            mGroup.addView(view, index, onCreateLayoutParams());
            return view;
        }
        private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
            return new StatusBarIconView(mContext, slot, null, blocked);
        }

StatusBarIconView

public class StatusBarIconView extends AnimatedImageView implements StatusIconDisplayable {

view.set(icon)的方法,看下可见性的处理。可以看到,需要是icon本身可见并且block为false。

        if (!visibilityEquals) {
            setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
        }
        return true;
    }

>2.特殊的icon(wifi,mobile)

wifi ,new wifi ,mobile ,new mobile 是4种不同的view

final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);

>>新旧icon显示的判断

那么到底显示新的还是旧的,咋判断的?如下,可以看到是statusBarPipelineFlags这个类决定的

        public IconManager(
                ViewGroup group,
                StatusBarLocation location,
                StatusBarPipelineFlags statusBarPipelineFlags,
                WifiUiAdapter wifiUiAdapter,
                MobileUiAdapter mobileUiAdapter,
                MobileContextProvider mobileContextProvider
        ) {
            mGroup = group;
            mStatusBarPipelineFlags = statusBarPipelineFlags;
            mMobileContextProvider = mobileContextProvider;
            mContext = group.getContext();
            mIconSize = mContext.getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.status_bar_icon_size);
            mLocation = location;

//是否是新的mobile icon
            if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
                // This starts the flow for the new pipeline, and will notify us of changes if
                // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
                mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
                MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
            } else {
                mMobileIconsViewModel = null;
            }
//是否是新的wifi icon
            if (statusBarPipelineFlags.runNewWifiIconBackend()) {
                // This starts the flow for the new pipeline, and will notify us of changes if
                // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
            } else {
                mWifiViewModel = null;
            }
        }

继续,可以看到,又是由FeatureFlags决定,这个东西是个接口,有两种实现

class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {

    fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)

    fun runNewMobileIconsBackend(): Boolean =
        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()

    fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)

    fun runNewWifiIconBackend(): Boolean =
        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()

如下,都是false

public class FeatureFlagsRelease implements FeatureFlags {
    public boolean isEnabled(@NotNull UnreleasedFlag flag) {
        return false;
    }

debug模式下,可以看到直接用的就是参数flag的默认的值,具体的可以点进去用到的flag查看,默认都是false的

public class FeatureFlagsDebug implements FeatureFlags {
    public boolean isEnabled(@NotNull UnreleasedFlag flag) {
        return isEnabledInternal(flag);
    }
    private boolean isEnabledInternal(@NotNull BooleanFlag flag) {
        int id = flag.getId();
        if (!mBooleanFlagCache.containsKey(id)) {
            mBooleanFlagCache.put(id,
                    readBooleanFlagInternal(flag, flag.getDefault()));
        }
        return mBooleanFlagCache.get(id);
    }

>>addMobileIcon

            StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
            StatusBarMobileView view = StatusBarMobileView
                    .fromContext(mobileContext, slot);
            return view;
    public static StatusBarMobileView fromContext(
            Context context,
            String slot
    ) {
        LayoutInflater inflater = LayoutInflater.from(context);
        StatusBarMobileView v = (StatusBarMobileView)
                inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
        v.setSlot(slot);
        v.init();
        v.setVisibleState(STATE_ICON);
        return v;
    }

布局看下

<com.android.systemui.statusbar.StatusBarMobileView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mobile_combo"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center_vertical" >

    <include layout="@layout/status_bar_mobile_signal_group_inner" />

</com.android.systemui.statusbar.StatusBarMobileView>

status_bar_mobile_signal_group_inner.xml

<?xml version="1.0" encoding="utf-8"?>

<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto" >

    <com.android.keyguard.AlphaOptimizedLinearLayout
        android:id="@+id/mobile_group"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:orientation="horizontal" >
//移动数据的接收发送,图片是向上向下的箭头
        <FrameLayout
            android:id="@+id/inout_container"
            android:layout_height="17dp"
            android:layout_width="wrap_content"
            android:layout_gravity="center_vertical">
            <ImageView
                android:id="@+id/mobile_in"
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"
                android:src="@drawable/ic_activity_down"
                android:visibility="gone"
                android:paddingEnd="2dp"
                />
            <ImageView
                android:id="@+id/mobile_out"
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"
                android:src="@drawable/ic_activity_up"
                android:paddingEnd="2dp"
                android:visibility="gone"
                />
        </FrameLayout>
        //移动数据的类型
        <ImageView
            android:id="@+id/mobile_type"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:layout_gravity="center_vertical"
            android:paddingStart="2.5dp"
            android:paddingEnd="1dp"
            android:visibility="gone" />
        <Space
            android:id="@+id/mobile_roaming_space"
            android:layout_height="match_parent"
            android:layout_width="@dimen/roaming_icon_start_padding"
            android:visibility="gone"
            />
        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical">
            <com.android.systemui.statusbar.AnimatedImageView
                android:id="@+id/mobile_signal"
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"
                systemui:hasOverlappingRendering="false"
                />
                //小的漫游图标,左上角一个R
            <ImageView
                android:id="@+id/mobile_roaming"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/stat_sys_roaming"
                android:contentDescription="@string/data_connection_roaming"
                android:visibility="gone" />
        </FrameLayout>
        //大的漫游图标,居中的R
        <ImageView
            android:id="@+id/mobile_roaming_large"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/stat_sys_roaming_large"
            android:contentDescription="@string/data_connection_roaming"
            android:visibility="gone" />
    </com.android.keyguard.AlphaOptimizedLinearLayout>
</merge>

addWifiIcon

        private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
            StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
            return view;
        }
    public static StatusBarWifiView fromContext(Context context, String slot) {
        LayoutInflater inflater = LayoutInflater.from(context);
        StatusBarWifiView v = (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null);
        v.setSlot(slot);
        v.init();
        v.setVisibleState(STATE_ICON);
        return v;
    }

8.3.onTuningChanged

        public void onTuningChanged(String key, String newValue) {
            if (!ICON_HIDE_LIST.equals(key)) {
                return;
            }
            mIconHideList.clear();
            //数据见补充1
            mIconHideList.addAll(StatusBarIconController.getIconHideList(mContext, newValue));

>1.getIconHideList

        static ArraySet<String> getIconHideList(Context context, String hideListStr) {
            ArraySet<String> ret = new ArraySet<>();
            //没有提供hide数据,就用默认配置的,有的话就解析
            String[] hideList = hideListStr == null
                    ? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)//见3.2.1
                    : hideListStr.split(",");
            for (String slot : hideList) {
                if (!TextUtils.isEmpty(slot)) {
                    ret.add(slot);
                }
            }
            return ret;
        }

8.4.addSystemIcon

        private void addSystemIcon(String slot, StatusBarIconHolder holder) {
            int viewIndex = mStatusBarIconList.getViewIndex(slot, holder.getTag());
            //看slot是否在要隐藏的集合里
            boolean hidden = mIconHideList.contains(slot);
            //见5.3.2,添加图标到容器
            mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
        }

9.BatteryMeterView

状态栏最右侧的电池图标

public class BatteryMeterView extends LinearLayout implements DarkReceiver {

9.1.构造方法

    public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //水平方向
        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL | Gravity.START);

        TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
                defStyle, 0);
        final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
                context.getColor(R.color.meter_background_color));
        mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
        mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
        atts.recycle();
        //显示百分比是否可用,配置里的,默认是true
        mShowPercentAvailable = context.getResources().getBoolean(
                com.android.internal.R.bool.config_battery_percentage_setting_available);

        setupLayoutTransition();
        //电池图标
        mBatteryIconView = new ImageView(context);
        mBatteryIconView.setImageDrawable(mDrawable);
        final MarginLayoutParams mlp = new MarginLayoutParams(
                getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
                getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
        //右侧有个margin
        mlp.setMargins(0, 0, 0,
                getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
        addView(mBatteryIconView, mlp);
        //见9.2
        updateShowPercent();
        mDualToneHandler = new DualToneHandler(context);
        // Init to not dark at all.
        onDarkChanged(new ArrayList<Rect>(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);

        setClipChildren(false);
        setClipToPadding(false);
    }

>1.mShowPercentMode

显示百分比的模式有4种

    private int mShowPercentMode = MODE_DEFAULT;

    public static final int MODE_DEFAULT = 0;
    public static final int MODE_ON = 1;
    public static final int MODE_OFF = 2;
    public static final int MODE_ESTIMATE = 3;

9.2.updateShowPercent

更新是否显示电池百分比

    void updateShowPercent() {
        //百分比控件不为空,说明正在显示,否则没有显示
        final boolean showing = mBatteryPercentView != null;
        //先读取系统设置,是否允许显示
        final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
                .getIntForUser(getContext().getContentResolver(),
                SHOW_BATTERY_PERCENT, 0, UserHandle.USER_CURRENT));
                
        boolean shouldShow =
        //状态栏的图标正常应该是这行条件
                (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
            //后边两种mode的改变是下拉状态栏的图标
                || mShowPercentMode == MODE_ON
                || mShowPercentMode == MODE_ESTIMATE;
        //电池状态不是未知的
        shouldShow = shouldShow && !mBatteryStateUnknown;

        if (shouldShow) {
            if (!showing) {
            //见补充1,加载百分比控件
                mBatteryPercentView = loadPercentView();
                if (mPercentageStyleId != 0) { // Only set if specified as attribute
                    mBatteryPercentView.setTextAppearance(mPercentageStyleId);
                }
                if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
                //见补充2
                updatePercentText();
                //添加文本到容器
                addView(mBatteryPercentView,
                        new ViewGroup.LayoutParams(
                                LayoutParams.WRAP_CONTENT,
                                LayoutParams.MATCH_PARENT));
            }
        } else {
        //不应该显示,并且正在显示,移除
            if (showing) {
                removeView(mBatteryPercentView);
                mBatteryPercentView = null;
            }
        }
    }

>1.loadPercentView

    private TextView loadPercentView() {
        return (TextView) LayoutInflater.from(getContext())
                .inflate(R.layout.battery_percentage_view, null);
    }

>2.updatePercentText

更新百分比文本内容

    void updatePercentText() {
        if (mBatteryStateUnknown) {
        //电池状态未知
            return;
        }
        //电池估算获取器为空
        if (mBatteryEstimateFetcher == null) {
            //见补充3
            setPercentTextAtCurrentLevel();
            return;
        }
        //百分比控件不为空(说明正在显示中)
        if (mBatteryPercentView != null) {
            //模式是 estimate并且非充电中
            if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
            //获取估算的电池信息
                mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
                        (String estimate) -> {
                    if (mBatteryPercentView == null) {
                        return;
                    }
                    if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
                        //估算模式,有值返回,直接显示
                        mEstimateText = estimate;
                        mBatteryPercentView.setText(estimate);
                        updateContentDescription();
                    } else {
                    //补充3
                        setPercentTextAtCurrentLevel();
                    }
                });
            } else {
                setPercentTextAtCurrentLevel();
            }
        } else {
            updateContentDescription();
        }
    }

>3.setPercentTextAtCurrentLevel

根据当前的level,设置百分比

    private void setPercentTextAtCurrentLevel() {
        if (mBatteryPercentView != null) {
            mEstimateText = null;
            //格式化为 x%
            String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
            if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
                mBatteryPercentView.setText(percentText);
            }
        }

        updateContentDescription();
    }

9.3.setPercentShowMode

此方法调用见9.4

    public void setPercentShowMode(@BatteryPercentMode int mode) {
        if (mode == mShowPercentMode) return;
        mShowPercentMode = mode;
        updateShowPercent();//见9.2
        updatePercentText();//见9.2.2
    }

9.4.QuickStatusBarHeader.java

>1.onFinishInflate

    protected void onFinishInflate() {
    //..
        // QS will always show the estimate, and BatteryMeterView handles the case where
        // it's unavailable or charging
        mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);

>2.updateBatteryMode

更新batteryMode,有两处调用:

  • onApplyWindowInsets方法
  • updateResources方法 参数说明:
  • mConfigShowBatteryEstimate 配置里,默认是false,390dp以上是true
  • mHasCenterCutout 就是圆角有没有裁剪过
    private void updateBatteryMode() {
        if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
            mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
        } else {
            mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ON);
        }
    }

9.5.onBatteryLevelChanged

电量变化回调,是在controller里调用的

    void onBatteryLevelChanged(int level, boolean pluggedIn) {
        mDrawable.setCharging(pluggedIn);
        mDrawable.setBatteryLevel(level);
        mCharging = pluggedIn;
        mLevel = level;
        updatePercentText();
    }

9.6.onIsOverheatedChanged

过热状态

    void onIsOverheatedChanged(boolean isOverheated) {
        boolean valueChanged = mIsOverheated != isOverheated;
        mIsOverheated = isOverheated;
        if (valueChanged) {
            updateContentDescription();
            // 过热状态的图标和正常的不一样,更新
            scaleBatteryMeterViews();
        }
    }

10.BatteryMeterViewController.java

10.1.构造方法

        //设置电池评估获取器
        mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
        //是否显示电池保护图标,默认配置是false
        mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));

        mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
        mSettingObserver = new SettingObserver(mMainHandler);
    }

10.2.onViewAttached

    protected void onViewAttached() {
        mConfigurationController.addCallback(mConfigurationListener);
        subscribeForTunerUpdates();
        //添加电池状态变化图标,见10.3
        mBatteryController.addCallback(mBatteryStateChangeCallback);
        //监听settings改变
        registerShowBatteryPercentObserver(mUserTracker.getUserId());
        registerGlobalBatteryUpdateObserver();
        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));

        mView.updateShowPercent();
    }

>1.监听settings改变

observer见补充2

    private void registerShowBatteryPercentObserver(int user) {
        mContentResolver.registerContentObserver(
                Settings.System.getUriFor(SHOW_BATTERY_PERCENT),
                false,
                mSettingObserver,
                user);
    }

    private void registerGlobalBatteryUpdateObserver() {
        mContentResolver.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
                false,
                mSettingObserver);
    }

>2.SettingObserver

        public void onChange(boolean selfChange, Uri uri) {
            super.onChange(selfChange, uri);
            //更新是否显示百分比
            mView.updateShowPercent();
            if (TextUtils.equals(uri.getLastPathSegment(),
                    Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME)) {
                //更新百分比文本内容
                mView.updatePercentText();
            }
        }

10.3.mBatteryStateChangeCallback

    private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
            new BatteryController.BatteryStateChangeCallback() {
                @Override// 电量变化
                public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
                    mView.onBatteryLevelChanged(level, pluggedIn);
                }

                @Override//省电模式变化
                public void onPowerSaveChanged(boolean isPowerSave) {
                    mView.onPowerSaveChanged(isPowerSave);
                }

                @Override//电池状态是否未知
                public void onBatteryUnknownStateChanged(boolean isUnknown) {
                    mView.onBatteryUnknownStateChanged(isUnknown);
                }

                @Override//是否过热
                public void onIsOverheatedChanged(boolean isOverheated) {
                    mView.onIsOverheatedChanged(isOverheated);
                }
            };

11.总结

这篇主要介绍了状态栏(非展开状态)的图标显示逻辑。

  • StatusBarView的添加
  • 通过CollapsedStatusBarFragment管理icon的显示
  • icon的管理类有2个,PhoneStatusBarPollicy以及StatusBarSignalPolicy
  • 可以在string里配置哪些icon不用显示