SystemUI之状态图标控制

3,570 阅读6分钟

本文是基于Android 10源码分析的。

SystemUI之StatusBar创建 介绍了状态栏有三部分组成

  1. 最左边的一部分显示运营商,时间,通知图标
  2. 最右边的一部分显示系统图标,它由状态图标(例如 wifi ,bt)和电池图标组成。
  3. 中间还有一块区域,暂时不讨论。

本文主要是讨论状态图标(例如 wifi ,bt)是如何被控制显示的。

状态栏图标控制器

状态栏上状态图标的控制,都要通过一个接口来实现,这个接口是StatusBarIconController,而它的实现类是 StatusBarIconControllerImpl

首先看下它的注释以及继承结构

/**
 * Receives the callbacks from CommandQueue related to icons and tracks the state of
 * all the icons. Dispatches this state to any IconManagers that are currently
 * registered with it.
 */
@Singleton
public class StatusBarIconControllerImpl extends StatusBarIconList 
                implements Tunable, CommandQueue.Callbacks, StatusBarIconController {}

注释说它接收来自CommandQueue关于图标的回调,然后分发给已经注册的IconManager。然而它的功能还不仅仅如此,SystemUI任何地方都可以调用它来给状态栏设置图标,因为它还实现了StatusBarIconController接口,这个接口部分内容如下

public interface StatusBarIconController {
    // 设置图标
    public void setIcon(String slot, int resourceId, CharSequence contentDescription);
    public void setIcon(String slot, StatusBarIcon icon);
    // 设置wifi图标
    public void setSignalIcon(String slot, WifiIconState state);
    // 设置手机信号图标
    public void setMobileIcons(String slot, List<MobileIconState> states);
    // 设置图标可见性
    public void setIconVisibility(String slot, boolean b);
}

有了这个概念后,现在来看下它的构造函数所做事

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
    
    public StatusBarIconControllerImpl(Context context) {
        mContext = context;
        
        // 通过父类保存状态栏上图标的名字
        super(context.getResources().getStringArray(
                com.android.internal.R.array.config_statusBarIcons));
                
        // 注册CommandQueue的回调
        SysUiServiceProvider.getComponent(context, CommandQueue.class)
                .addCallback(this);
        
        // 实现黑名单的功能,原理是监听数据库中黑名单的值
        Dependency.get(TunerService.class).addTunable(this, ICON_BLACKLIST);

    }

这里主要做了三件事

  1. 保存状态栏上图标的名字。
  2. CommandQueue 注册回调。
  3. 监听数据库黑名单。

黑名单的功能很简单,就是监听数据库中关于黑名单的URI的变化,然后更新黑名单列表,然后在设置图标的时候去查询黑名单,再决定如何设置。

CommandQueue是一个Binder类,它被StatusBar#start()注册到了StatusBarManagerService中,用于接收服务端的消息,其实就相当于在服务端注册了一个回调。而StatusBarIconControllerImplCommandQueue注册回调,是为了获取关于图标的消息,以便它能正确地在状态栏上设置图标。

现在来看下保存的图标名字的数组 config_statusBarIcons

    // frameworks/base/core/res/res/values/config.xml
    
    <string-array name="config_statusBarIcons">
        <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_rotate</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_headset</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_data_saver</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_ime</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_sync_failing</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_sync_active</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_nfc</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_tty</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_speakerphone</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_camera</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_microphone</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_location</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item>
    </string-array>

这个数组不仅仅定义了图标的名字,而且还定义了图标的顺序,例如 wifi 肯定在电池图标之前显示。

注册图标管理器

本文要讨论的是状态图标的控制,根据上面讲的,那得首先向StatusBarIconControllerImpl注册一个图标管理器,根据前文就可猜测,这个是在CollapsedStatusBarFragment中实现的

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
    
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // ...
        
        // R.id.statusIcons代表状态图标区,例如bt, wifi
        // 创建一个状态图标区的图标管理器
        mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));

        // 向StatusBarIconControllerImpl注册图标管理器
        Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);  
        
        // ...
    }

设置状态图标

一切准备就绪,那么现在来看下状态图标的设置如何实现的呢?在StatusBar#start()中,它通过一些策略来设置的

    // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
    
    public void start() {
        // ...
        
        // 调用图标策略安装/更新所有图标
        mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
        mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController);
        
        // ...
    }        

所有的状态图标的设置都是在这两个策略类中。来看下手机信号图标如何设置的,它在StatusBarSignalPolicy

    public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
        mContext = context;

        // 获取信号图标的名字
        mSlotMobile   = mContext.getString(com.android.internal.R.string.status_bar_mobile);

        // 这个就是StatusBarIconContrllerImpl对象
        mIconController = iconController;
        
        // 注册网络监听
        mNetworkController = Dependency.get(NetworkController.class);
        mNetworkController.addCallback(this);
    }

要设置某一个图标,首先要获取图标的名字,这个名字可以从上面提到的数组中获取。

然后注册了网络监听器,这样就可以获得关于手机信号的事件

    @Override
    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
            int qsType, boolean activityIn, boolean activityOut, int volteIcon,
            String typeContentDescription, String description, boolean isWide,
            int subId, boolean roaming) {
        // ...
        
        // 设置信号图标,第一个参数表示图标名字,第二个参数表示状态
        mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates));

        // ..
    }

可以看到是通过StatusBarIconController#setMobileIcons()实现的,具体的细节就不深入的。

通过StatusBarIconController接口设置图标的套路都是一样的

  1. 获取图标名字
  2. 监听事件
  3. 通过StatusBarIconController相应的方法设置图标。

设置无SIM卡图标

公司之前需要设置一个没有SIM时的图标,这个接口其实系统已经提供,就在 StatusBarSignalPolicy

     public void setNoSims(boolean show, boolean simDetected) {
        String mobileSlot = mContext.getString(com.android.internal.R.string.status_bar_mobile);
        if (show && !simDetected) {
            mIconController.setIcon(mobileSlot, R.drawable.ic_no_sim_black_24dp, "no_sim");
            mIconController.setIconVisibility(mobileSlot, true);
        } else {
            mIconController.setIconVisibility(mobileSlot, false);
        }
     }

按照套路来,so easy!