Android12 CarSystemBar源码分析<二> 顶部

2022-11-11  本文已影响0人  AFinalStone

前言

上一篇文章我们我们已经把CarSystemBar从启动到构建视图,再到将视图添加到Window的流程整理分析完毕了,结合代码我们也知道,默认情况下在车载系统中只显示顶部栏和底部栏视图的。

一、顶部栏视图内容的构建过程

1、结合上篇文章,我们再来简单梳理一下顶部栏视图内容的构建过程。

package/app/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBar.java

public class CarSystemBar extends SystemUI implements CommandQueue.Callbacks {
    private ViewGroup mTopSystemBarWindow;//顶部栏视图容器
    private CarSystemBarView mTopSystemBarView;//顶部栏视图内容
   public void start() {
        createSystemBar(result);
    }

    private void createSystemBar(RegisterStatusBarResult result) {
        buildNavBarWindows();//构建视图对象容器
        buildNavBarContent();//构建视图对象内容,本次我们重点分析的内容
        attachNavBarWindows();//将视图对象添加到Window中,本次不做分析
    }

    //构建视图对象容器
    private void buildNavBarWindows() {
        mTopSystemBarWindow = mCarSystemBarController.getTopWindow();
    }

    //构建视图对象内容,本次我们重点分析的内容
    private void buildNavBarContent() {
        mTopSystemBarView = mCarSystemBarController.getTopBar(isDeviceSetupForUser());
        if (mTopSystemBarView != null) {
            mSystemBarConfigs.insetSystemBar(SystemBarConfigs.TOP, mTopSystemBarView);
            mHvacController.registerHvacViews(mTopSystemBarView);
            mTopSystemBarWindow.addView(mTopSystemBarView);
        }
    }

}

简单梳理一下上面的流程,CarSystemBar类被启动 > start方法被调用 > buildNavBarWindows方法被调用 > buildNavBarContent方法被调用。其中buildNavBarContent方法就是构建顶部栏视图内容mTopSystemBarView的关键,该方法内部是通过CarSystemBarController的getTopBar方法来获取的。

2、CarSystemBarController的getTopBar方法关键代码如下所示:

package/app/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBarController.java

@SysUISingleton
public class CarSystemBarController {

    @Nullable
    public CarSystemBarView getTopBar(boolean isSetUp) {
        if (!mShowTop) {
            return null;
        }
        //进一步调用CarSystemBarViewFactory的getTopBar方法
        mTopView = mCarSystemBarViewFactory.getTopBar(isSetUp);
        ...代码省略...
        return mTopView;
    }
}

getTopBar方法内部继续调用CarSystemBarViewFactory的getTopBar方法。

3、CarSystemBarViewFactory的getTopBar方法关键代码如下所示:

package/app/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBarViewFactory.java

@SysUISingleton
public class CarSystemBarViewFactory {
    private final ArrayMap<Type, CarSystemBarView> mCachedViewMap = new ArrayMap<>(Type.values().length);
    private static final ArrayMap<Type, Integer> sLayoutMap = setupLayoutMapping();
    private static ArrayMap<Type, Integer> setupLayoutMapping() {
        ArrayMap<Type, Integer> map = new ArrayMap<>();
        map.put(Type.TOP, R.layout.car_top_system_bar);//顶部栏视图布局
        ...代码省略...
        return map;
    }
    private final QuickControlsEntryPointsController mQuickControlsEntryPointsController;//顶部栏图标控制器
    private final ReadOnlyIconsController mReadOnlyIconsController;//顶部栏只读图标控制器
    //获取顶部栏视图内容
    public CarSystemBarView getTopBar(boolean isSetUp) {
        return getBar(isSetUp, Type.TOP, Type.TOP_UNPROVISIONED);
    }

    //getBar继续调用getBarCached方法
    private CarSystemBarView getBar(boolean isSetUp, Type provisioned, Type unprovisioned) {
        CarSystemBarView view = getBarCached(isSetUp, provisioned, unprovisioned);
        if (view == null) {
            String name = isSetUp ? provisioned.name() : unprovisioned.name();
            Log.e(TAG, "CarStatusBar failed inflate for " + name);
            throw new RuntimeException(
                    "Unable to build " + name + " nav bar due to missing layout");
        }
        return view;
    }

  //getBarCached方法最终是从sLayoutMap中获取具体的视图内容
    private CarSystemBarView getBarCached(boolean isSetUp, Type provisioned, Type unprovisioned) {
        Type type = isSetUp ? provisioned : unprovisioned;
        if (mCachedViewMap.containsKey(type)) {
            return mCachedViewMap.get(type);
        }
        //从sLayoutMap中获取对应的布局文件资源id
        @LayoutRes int barLayout = sLayoutMap.get(type);
        //将布局文件转化为CarSystemBarView类型的View对象。
        CarSystemBarView view = (CarSystemBarView) View.inflate(mContext, barLayout,
                /* root= */ null);
        ...代码省略...
    }
}

CarSystemBarViewFactory这个类的getTopBar方法会继续调用getBarCached方法,getBarCached首先从类型为ArrayMap<Type, CarSystemBarView>的缓存mCachedViewMap中获取缓存对象,如果存在直接返回,如果不存在则继续从类型为ArrayMap<Type, Integer>的sLayoutMap中获取具体布局文件资源id,结合代码我们可以知道顶部栏对应id为R.layout.car_top_system_bar的布局文件,getBarCached方法首先使用View.Inflate方法将car_top_system_bar.xml布局文件转化类型为CarSystemBarView的view。

二、顶部栏视图内容所对应的布局文件和效果图

1、来看一下CarSystemBar中顶部栏mTopSystemBarView视图内容所对应的id为R.layout.car_top_system_bar的布局文件:

framework/base/package/SystemUI/res/layout/car_top_system_bar.xml

<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.car.systembar.CarSystemBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/car_top_bar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/system_bar_background"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layoutDirection="ltr">

        <LinearLayout
            android:id="@+id/qc_entry_points_container"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_alignParentStart="true"
        />

        <FrameLayout
            android:id="@+id/clock_container"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingStart="@dimen/car_padding_2"
            android:paddingEnd="@dimen/car_padding_2"
            android:layout_centerInParent="true">
            <com.android.systemui.statusbar.policy.Clock
                android:id="@+id/clock"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:elevation="5dp"
                android:singleLine="true"
                android:textAppearance="@style/TextAppearance.SystemBar.Clock"
                systemui:amPmStyle="normal"
            />
        </FrameLayout>

        <LinearLayout
            android:id="@+id/read_only_icons_container"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/clock_container"
        />

        <include layout="@layout/mic_privacy_chip"
            android:layout_width="@dimen/privacy_chip_width"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/read_only_icons_container"
            android:layout_toLeftOf="@id/user_name_container" />

        <FrameLayout
            android:id="@+id/user_name_container"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:layout_marginTop="@dimen/car_padding_2"
        >
            <com.android.systemui.car.systembar.CarSystemBarButton
                android:id="@+id/user_name"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginEnd="@dimen/car_padding_2"
                android:background="@drawable/status_icon_background"
                android:gravity="center_vertical">
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="horizontal"
                    android:layout_marginStart="@dimen/car_padding_2"
                    android:layout_marginEnd="@dimen/car_padding_2"
                    android:gravity="center_vertical"
                >
                    <ImageView
                        android:id="@+id/user_avatar"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:src="@drawable/car_ic_user_icon"
                        android:tint="@color/system_bar_icon_color_with_selection"
                        android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
                    />
                    <TextView
                        android:id="@+id/user_name_text"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:gravity="center_vertical"
                        android:textAppearance="@style/TextAppearance.SystemBar.Username"
                        android:singleLine="true"
                        android:maxWidth="@dimen/car_system_bar_user_name_max_width"
                        android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
                    />
                </LinearLayout>
            </com.android.systemui.car.systembar.CarSystemBarButton>
        </FrameLayout>
    </RelativeLayout>

</com.android.systemui.car.systembar.CarSystemBarView>

2、car_top_system_bar.xml所构建是视图其实就是车载系统最终显示在桌面上的状态栏视图。

image.png

三、顶部栏视图内容中状态栏图标容器的构建过程

1、理解了car_top_system_bar.xml具体内容和视图效果之后,重新回到CarSystemBarViewFactory类的getBarCached方法,继续往下看

@SysUISingleton
public class CarSystemBarViewFactory {

    private CarSystemBarView getBarCached(boolean isSetUp, Type provisioned, Type unprovisioned) {
        ...代码省略...
        //为空调按钮设置点击事件
        view.setupHvacButton();
        ...代码省略...
    }
}

2、该方法会调用CarSystemBarView的setupHvacButton方法为空调按钮设置点击事件:

public class CarSystemBarView extends LinearLayout {
    private HvacButton mHvacButton;
    @Override
    public void onFinishInflate() {
        ...代码省略...
        mHvacButton = findViewById(R.id.hvac);//获取空调按钮
}

   void setupHvacButton() {
        if (mHvacButton != null) {
            mHvacButton.setOnClickListener(this::onHvacClick);//设置点击监听事件
        }
    }
}

由于car_top_system_bar.xml文件中并不包含空调按钮,因此这里mHvacButton按钮为空,由于状态栏上没有空调按钮,这里我们暂不做深入的分析。

3、继续往下看CarSystemBarViewFactory类的getBarCached方法

@SysUISingleton
public class CarSystemBarViewFactory {

    private final QuickControlsEntryPointsController mQuickControlsEntryPointsController;//存储快捷图标容器控件的控制器

    private CarSystemBarView getBarCached(boolean isSetUp, Type provisioned, Type unprovisioned) {
        //构建顶部栏视图内容对象
        ...代码省略...
        //为存储快捷图标的容器控件设置控制器
        view.setupQuickControlsEntryPoints(mQuickControlsEntryPointsController, isSetUp);
        ...代码省略...
    }
}

该方法会调用CarSystemBarView的setupQuickControlsEntryPoints方法为存储快捷图标的容器控件设置控制器。

4、CarSystemBarView类中和setupQuickControlsEntryPoints相关的关键代码:

public class CarSystemBarView extends LinearLayout {
    private ViewGroup mQcEntryPointsContainer;//快捷图标容器
    @Override
    public void onFinishInflate() {
        ...代码省略...
        //car_top_system_bar.xml中id为qc_entry_points_container的LinearLayout控件
        mQcEntryPointsContainer = findViewById(R.id.qc_entry_points_container);
}
  //为快捷图标容器设置控制器
    void setupQuickControlsEntryPoints(QuickControlsEntryPointsController quickControlsEntryPointsController,
            boolean isSetUp) {
        if (mQcEntryPointsContainer != null) {
        quickControlsEntryPointsController.addIconViews(mQcEntryPointsContainer, isSetUp);
        }
    }
}

setupQuickControlsEntryPoints方法用到了mQcEntryPointsContainer这个快捷图标容器对象,该对象对应的是car_top_system_bar.xml布局文件会将id为qc_entry_points_container的LinearLayout控件:

5、mQcEntryPointsContainer在car_top_system_bar.xml布局文件中的位置如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.car.systembar.CarSystemBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/car_top_bar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/system_bar_background"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layoutDirection="ltr">

        <LinearLayout
            android:id="@+id/qc_entry_points_container"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_alignParentStart="true"
        />
      ...代码省略...
    </RelativeLayout>
</com.android.systemui.car.systembar.CarSystemBarView>

通过布局文件的位置可以知道id为qc_entry_points_container的LinearLayout控件位于顶部栏的最左边,这很好的对应了上面在车载系统上显示的效果图。

6、重新回到CarSystemBarView的setupQuickControlsEntryPoints方法中,该方法不但用到了mQcEntryPointsContainer,还用到了QuickControlsEntryPointsController这个快捷图标控制器。:

package/app/Car/SystemUI/src/com/android/systemui/car/statusicon/ui/QuickControlsEntryPointsController.java

public class QuickControlsEntryPointsController extends StatusIconGroupContainerController {
    @Inject
    QuickControlsEntryPointsController(
            Context context,
            @Main Resources resources,
            CarServiceProvider carServiceProvider,
            BroadcastDispatcher broadcastDispatcher,
            ConfigurationController configurationController,
            Map<Class<?>, Provider<StatusIconController>> iconControllerCreators) {
        super(context, resources, carServiceProvider, broadcastDispatcher, configurationController,
                iconControllerCreators);
    }

    @Override
    @ArrayRes
    protected int getStatusIconControllersStringArray() {
        return R.array.config_quickControlsEntryPointIconControllers;
    }

    @Override
    @LayoutRes
    public int getButtonViewLayout() {
        return R.layout.car_qc_entry_points_button;
    }
}

QuickControlsEntryPointsController这个类继承自StatusIconGroupContainerController,这里我们先简单看下不做分析,因为想要完全理解这个类,我们就要先理解它的父类。

7、StatusIconGroupContainerController是个抽象类,该类的代码如下所示:

package/app/Car/SystemUI/src/com/android/systemui/car/statusicon/ui/StatusIconGroupContainerController .java

public abstract class StatusIconGroupContainerController {
    private final Context mContext;
    private final Resources mResources;
    private final CarServiceProvider mCarServiceProvider;
    private final BroadcastDispatcher mBroadcastDispatcher;
    private final ConfigurationController mConfigurationController;
    private final Map<Class<?>, Provider<StatusIconController>> mIconControllerCreators;
    private final String mIconTag;
    private final @ColorInt int mIconNotHighlightedColor;
    private final String[] mStatusIconControllerNames;

    public StatusIconGroupContainerController(
            Context context,
            @Main Resources resources,
            CarServiceProvider carServiceProvider,
            BroadcastDispatcher broadcastDispatcher,
            ConfigurationController configurationController,
            Map<Class<?>, Provider<StatusIconController>> iconControllerCreators) {
        mContext = context;
        mResources = resources;
        mCarServiceProvider = carServiceProvider;
        mBroadcastDispatcher = broadcastDispatcher;
        mConfigurationController = configurationController;
        mIconControllerCreators = iconControllerCreators;
        //<string name="qc_icon_tag" translatable="false">TAG_QC_ICON</string>
        mIconTag = mResources.getString(R.string.qc_icon_tag);
        //<color name="status_icon_not_highlighted_color">#FFFFFF</color>
        mIconNotHighlightedColor = mContext.getColor(R.color.status_icon_not_highlighted_color);
        mStatusIconControllerNames = mResources.getStringArray(
                getStatusIconControllersStringArray());//获取状态栏图标控制器类名字符串数组
    }

    private static <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) {
        try {
            Class<?> clazz = Class.forName(className);
            Provider<T> provider = creators.get(clazz);
            return provider == null ? null : provider.get();
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * 获取包含了StatusIcon中的按钮视图的布局资源id
     */
    @LayoutRes
    public int getButtonViewLayout() {
        return R.layout.default_status_icon;
    }

    /**
     * 添加新的图标视图容器
     * @param containerViewGroup
     */
    public void addIconViews(ViewGroup containerViewGroup) {
        addIconViews(containerViewGroup, /* shouldAttachPanel= */ true);
    }

    /**
     * 为containerViewGroup添加快捷图标
     * @param containerViewGroup 添加快捷开关到containerViewGroup所对应的容器中
     * @param shouldAttachPanel 是否允许弹出控制台弹窗
     */
    public void addIconViews(ViewGroup containerViewGroup, boolean shouldAttachPanel) {

        LayoutInflater li = LayoutInflater.from(mContext);

        //遍历状态栏图标控制器类名数组
        for (String clsName : mStatusIconControllerNames) {
            //通过状态栏图标控制器类名获取实例对象
            StatusIconController statusIconController = getStatusIconControllerByName(clsName);
            //构建状态栏图标视图
            View entryPointView = li.inflate(getButtonViewLayout(), containerViewGroup, /* attachToRoot= */ false);
            //qc_icon_tag = TAG_QC_ICON
            //通过Tag获取视图对象中tag为TAG_QC_ICON的ImageView控件
            ImageView statusIconView = entryPointView.findViewWithTag(mIconTag);
            //将statusIconView控件注册到状态栏图标控制器中
            statusIconController.registerIconView(statusIconView);
            //设置过滤颜色
            statusIconView.setColorFilter(mIconNotHighlightedColor);

            //如果允许弹出控制台弹窗,且当前状态栏图标控制器包含控制器弹窗布局文件id不等于PANEL_CONTENT_LAYOUT_NONE
            if (shouldAttachPanel && statusIconController.getPanelContentLayout() != PANEL_CONTENT_LAYOUT_NONE) {
                //构建状态栏图标控制台弹窗控制器
                StatusIconPanelController panelController = new StatusIconPanelController(mContext,
                        mCarServiceProvider, mBroadcastDispatcher, mConfigurationController);
                //将状态栏图标控制台弹窗控制器和entryPointView和statusIconController进行关联
                panelController.attachPanel(entryPointView,
                        statusIconController.getPanelContentLayout(),
                        statusIconController.getPanelWidth());
            }
            containerViewGroup.addView(entryPointView);
        }
    }

    /**
     * 获取状态栏图标控制器类名字符串数组
     * @return
     */
    @ArrayRes
    protected abstract int getStatusIconControllersStringArray();

    /**
     * 通过状态栏图标控制器类名获取实例对象
     * @param className
     * @return
     */
    private StatusIconController getStatusIconControllerByName(String className) {
        try {
            //dagger2依赖注入获取实例对象
            StatusIconController statusIconController = resolveStatusIconController(className);
            if (statusIconController == null) {
                //通过反射回去实例对象
                Constructor constructor = Class.forName(className).getConstructor(Context.class);
                statusIconController = (StatusIconController) constructor.newInstance(this);
            }

            return statusIconController;
        } catch (ClassNotFoundException
                | NoSuchMethodException
                | IllegalAccessException
                | InstantiationException
                | InvocationTargetException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 依赖注入
     * @param className
     * @return
     */
    private StatusIconController resolveStatusIconController(String className) {
        return resolve(className, mIconControllerCreators);
    }
}

StatusIconGroupContainerController有以下几点值得我们注意:

1)在构造方法中调用getStatusIconControllersStringArray抽象方法,为状态栏图标控制器类名字符串数组mStatusIconControllerNames赋值,QuickControlsEntryPointsController有实现这个方法。

2)getButtonViewLayout方法返回将要包含在StatusIcon中的按钮视图的布局资源id,QuickControlsEntryPointsController有重写这个方法。

3)addIconViews(ViewGroup containerViewGroup, boolean shouldAttachPanel)方法,这个方法就是前面我们在CarSystemBarView的setupQuickControlsEntryPoints方法中所调用的方法。

8、看完前面的三个关键点,我们再重新回顾一下QuickControlsEntryPointsController这个类的代码:

public class QuickControlsEntryPointsController extends StatusIconGroupContainerController {
    @Inject
    QuickControlsEntryPointsController(
            Context context,
            @Main Resources resources,
            CarServiceProvider carServiceProvider,
            BroadcastDispatcher broadcastDispatcher,
            ConfigurationController configurationController,
            Map<Class<?>, Provider<StatusIconController>> iconControllerCreators) {
        super(context, resources, carServiceProvider, broadcastDispatcher, configurationController,
                iconControllerCreators);
    }
    /**
     * 获取状态栏图标控制器类名字符串数组
     * @return
     */
    @Override
    @ArrayRes
    protected int getStatusIconControllersStringArray() {
        return R.array.config_quickControlsEntryPointIconControllers;
    }
    /**
     * 获取将要包含在StatusIcon中的按钮视图的布局资源id
     */
    @Override
    @LayoutRes
    public int getButtonViewLayout() {
        return R.layout.car_qc_entry_points_button;
    }
}

QuickControlsEntryPointsController首先为构造方法添加了@Inject注解,以便支持dagger2框架依赖注入。
实现了getStatusIconControllersStringArray方法,返回R.array.config_quickControlsEntryPointIconControllers字符串数组资源:

    <string-array name="config_quickControlsEntryPointIconControllers" translatable="false">
        <item>com.android.systemui.car.statusicon.ui.BluetoothStatusIconController</item>
        <item>com.android.systemui.car.statusicon.ui.SignalStatusIconController</item>
        <item>com.android.systemui.car.statusicon.ui.DisplayStatusIconController</item>
    </string-array>

根据名字我们可以知道分别是蓝牙图标状态控制器,信号图标状态控制器,显示器亮度图标状态控制器。QuickControlsEntryPointsController还重写了getButtonViewLayout方法,返回R.layout.car_qc_entry_points_button布局文件资源:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="@dimen/car_quick_controls_entry_points_button_width"
    android:layout_height="match_parent"
    style="@style/QuickControlEntryPointButton">
    <ImageView
        android:layout_width="@dimen/car_quick_controls_entry_points_icon_width"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:tag="@string/qc_icon_tag"/>
</FrameLayout>
<string name="qc_icon_tag" translatable="false">TAG_QC_ICON</string>

该布局文件的根控件为FrameLayout,且内部有一个tag等于TAG_QC_ICON的ImageView。

9、到这里我们对于StatusIconGroupContainerController的addIconViews方法应该有一个比较明确的认知了,在CarSystemBarView的setupQuickControlsEntryPoints方法中

        quickControlsEntryPointsController.addIconViews(mQcEntryPointsContainer, isSetUp);

被调用的其实是StatusIconGroupContainerController的addIconViews方法。

1)addIconViews方法前半段代码分析:

    public void addIconViews(ViewGroup containerViewGroup, boolean shouldAttachPanel) {

        LayoutInflater li = LayoutInflater.from(mContext);

        //遍历状态栏图标控制器类名数组
        for (String clsName : mStatusIconControllerNames) {
            //通过状态栏图标控制器类名获取实例对象
            StatusIconController statusIconController = getStatusIconControllerByName(clsName);
            //构建状态栏图标视图
            View entryPointView = li.inflate(getButtonViewLayout(), containerViewGroup, /* attachToRoot= */ false);
            //qc_icon_tag = TAG_QC_ICON
            //通过Tag获取视图对象中tag为TAG_QC_ICON的ImageView控件
            ImageView statusIconView = entryPointView.findViewWithTag(mIconTag);
            //将statusIconView控件注册到状态栏图标控制器中
            statusIconController.registerIconView(statusIconView);
            //设置过滤颜色
            statusIconView.setColorFilter(mIconNotHighlightedColor);
            ...代码省略...
        }
    }

该方法首先会遍历状态栏图标控制器类名字符串数组mStatusIconControllerNames,
mStatusIconControllerNames最初是通过抽象方法getStatusIconControllersStringArray获得的,由于QuickControlsEntryPointsController重写了这个方法,所以这里得到的字符串数组内容应该是:

mStatusIconControllerNames = ["com.android.systemui.car.statusicon.ui.BluetoothStatusIconController"
       ,"com.android.systemui.car.statusicon.ui.SignalStatusIconController"
       ,"com.android.systemui.car.statusicon.ui.DisplayStatusIconController"]

这意味着mStatusIconControllerNames字符串数组中存放的依次是蓝牙图标状态控制器类名,信号图标状态控制器类名,显示器亮度图标状态控制器类名。遍历该数组的时候,首先调用getStatusIconControllerByName方法,通过类名获取状态栏控制器实例对象statusIconController,再调用LayoutInflater的inflate方法构建car_qc_entry_points_button.xml所对应的状态栏图标视图entryPointView,再通过Tag获取视图对象中tag为TAG_QC_ICON的ImageView控件statusIconView,紧接着又调用刚刚获取到的StatusIconController实例对象的registerIconView方法,将statusIconView控件注册到状态栏图标控制器中,以便该控制器来动态控制图标的状态,最后还调用setColorFilter方法为statusIconView设置过滤颜色。

2)addIconViews方法的后半段代码分析:

    public void addIconViews(ViewGroup containerViewGroup, boolean shouldAttachPanel) {

        LayoutInflater li = LayoutInflater.from(mContext);
        //遍历状态栏图标控制器类名数组:蓝牙图标状态控制器类名、信号图标状态控制器类名、屏幕亮度图标状态控制器类名
        for (String clsName : mStatusIconControllerNames) {
            ...代码省略...
            //如果允许弹出控制台弹窗,且当前状态栏图标控制器包含控制器弹窗布局文件id不等于PANEL_CONTENT_LAYOUT_NONE
            if (shouldAttachPanel && statusIconController.getPanelContentLayout() != PANEL_CONTENT_LAYOUT_NONE) {
                //构建状态栏图标控制台弹窗控制器
                StatusIconPanelController panelController = new StatusIconPanelController(mContext,
                        mCarServiceProvider, mBroadcastDispatcher, mConfigurationController);
                //将状态栏图标控制台弹窗控制器和entryPointView和statusIconController进行关联
                panelController.attachPanel(entryPointView,
                        statusIconController.getPanelContentLayout(),
                        statusIconController.getPanelWidth());
            }
            containerViewGroup.addView(entryPointView);
        }
    }

首先判断是否允许弹出控制台弹窗,且当前状态栏图标控制器包含控制器弹窗布局文件id不等于PANEL_CONTENT_LAYOUT_NONE,如果条件满足则会创建状态栏图标控制台弹窗控制器,并将状态栏图标控制台弹窗控制器和entryPointView和statusIconController进行关联,最后会将entryPointView添加到containerViewGroup中。

虽然分析完了StatusIconGroupContainerController的addIconViews方法,但是里面用到的蓝牙图标状态控制器BluetoothStatusIconController、信号图标状态控制器SignalStatusIconController、显示屏亮度状态控制器DisplayStatusIconController、图标控制台弹窗控制器StatusIconPanelController,还需要我们进一步分析。

四、状态栏图标状态控制器

1、由于蓝牙图标状态控制器BluetoothStatusIconController、信号图标状态控制器SignalStatusIconController、显示屏亮度状态控制器DisplayStatusIconController三者都继承自抽象类StatusIconController,这里我们先来看下该类的代码:

package/app/Car/SystemUI/src/com/android/systemui/car/statusicon/ui/StatusIconController.java

public abstract class StatusIconController {
    public static final int PANEL_CONTENT_LAYOUT_NONE = -1;

    private final StatusIconData mStatusIconData = new StatusIconData();
    private final MutableLiveData<StatusIconData> mStatusIconLiveData =
            new MutableLiveData<>(mStatusIconData);
    private final Map<ImageView, Observer<StatusIconData>> mObserverMap = new HashMap<>();

    /**
     * 为当前控制器注册该控制器将要控制的ImageView图标
     *
     * @param view
     */
    public final void registerIconView(ImageView view) {
        if (mObserverMap.containsKey(view)) return;

        Observer<StatusIconData> observer = statusIconData -> updateIconView(view, statusIconData);
        mObserverMap.put(view, observer);
        mStatusIconLiveData.observeForever(observer);
    }

    /**
     * 将图标从控制器中注销
     */
    public final void unregisterIconView(ImageView view) {
        Observer<StatusIconData> observer = mObserverMap.remove(view);
        if (observer != null) {
            mStatusIconLiveData.removeObserver(observer);
        }
    }

    /**
     * 返回显示的drawable图标文件
     */
    public Drawable getIconDrawableToDisplay() {
        return mStatusIconData.getIconDrawable();
    }

    /**
     * 设置显示的drawable图标文件
     */
    protected final void setIconDrawableToDisplay(Drawable drawable) {
        mStatusIconData.setIconDrawable(drawable);
    }

    /**
     * 设置图标的显示状态,图标默认为显示状态
     */
    protected final void setIconVisibility(boolean isVisible) {
        mStatusIconData.setIsIconVisible(isVisible);
    }

    /**
     * 控制器状态发生了变化,更新图标数据
     */
    protected void onStatusUpdated() {
        mStatusIconLiveData.setValue(mStatusIconData);
    }


    /**
     * 结合图标数据更新对应的图标状态
     *
     * @param view
     * @param data
     */
    protected void updateIconView(ImageView view, StatusIconData data) {
        view.setImageDrawable(data.getIconDrawable());
        view.setVisibility(data.getIsIconVisible() ? View.VISIBLE : View.GONE);
    }

    /**
     * 返回当前图标被点击时候弹出的控制面板弹窗所对应的布局文件资源id。默认返回{@link #PANEL_CONTENT_LAYOUT_NONE},
     * 图标被点击不会触发控制面板弹窗
     */
    @LayoutRes
    protected int getPanelContentLayout() {
        return PANEL_CONTENT_LAYOUT_NONE;
    }

    /**
     * 返回图标被点击时弹出的控制面板弹窗宽度资源id,默认为800dp
     * <dimen name="car_status_icon_panel_default_width">800dp</dimen>
     */
    @DimenRes
    protected int getPanelWidth() {
        return R.dimen.car_status_icon_panel_default_width;
    }

    /**
     * 更新状态,供子类实现
     */
    protected abstract void updateStatus();

    /**
     * 判断view是否已经被注册
     *
     * @param view
     * @return
     */
    @VisibleForTesting
    boolean isViewRegistered(ImageView view) {
        return mObserverMap.containsKey(view);
    }
}

2、蓝牙图标状态控制器BluetoothStatusIconController类:

package/app/Car/SystemUI/src/com/android/systemui/car/statusicon/ui/BluetoothStatusIconController.java

public class BluetoothStatusIconController extends StatusIconController implements
        BluetoothController.Callback {
    private final Context mContext;
    //蓝牙控制器,主要时通过这个控制器的回调方法来监听蓝牙的状态以设置对应的蓝牙图标状态
    private final BluetoothController mBluetoothController;

    private boolean mBluetoothEnabled;//蓝牙是否可用(是否开启)
    private boolean mBluetoothConnected;//蓝牙是否连接
    private Drawable mBluetoothOffDrawable;//蓝牙未开启状态所对应的Drawable
    private Drawable mBluetoothOnDisconnectedDrawable;//蓝牙断开状态多对应的Drawable
    private Drawable mBluetoothOnConnectedDrawable;//蓝牙连接状态所对应的Drawable

    @Inject
    BluetoothStatusIconController(
            Context context,
            @Main Resources resources,
            BluetoothController bluetoothController) {
        mContext = context;
        mBluetoothController = bluetoothController;
         //为蓝牙图标各种状态的Drawable对象进行赋值。
        mBluetoothOffDrawable = resources.getDrawable(
                R.drawable.ic_bluetooth_status_off, /* theme= */ null);
        mBluetoothOnDisconnectedDrawable = resources.getDrawable(
                R.drawable.ic_bluetooth_status_on_disconnected, /* theme= */ null);
        mBluetoothOnConnectedDrawable = resources.getDrawable(
                R.drawable.ic_bluetooth_status_on_connected, /* theme= */ null);
        //添加回调方法
        mBluetoothController.addCallback(this);
    }

    //更新蓝牙图标的状态
    @Override
    protected void updateStatus() {
        if (!mBluetoothEnabled) {
            //蓝牙不可用(未开启)
            setIconDrawableToDisplay(mBluetoothOffDrawable);
        } else if (mBluetoothConnected) {
            //蓝牙处于连接状态
            setIconDrawableToDisplay(mBluetoothOnConnectedDrawable);
        } else {
            //蓝牙处于断开状态
            setIconDrawableToDisplay(mBluetoothOnDisconnectedDrawable);
        }
        onStatusUpdated();
    }

    //BluetoothController的回调方法
    @Override
    public void onBluetoothStateChange(boolean enabled) {
        //蓝牙是否可用(是否开启)
        mBluetoothEnabled = enabled;
        updateStatus();
    }

    //BluetoothController的回调方法
    @Override
    public void onBluetoothDevicesChanged() {
        //蓝牙是否连接
        mBluetoothConnected = !mBluetoothController.getConnectedDevices().isEmpty();
        updateStatus();
    }

    @VisibleForTesting
    Drawable getBluetoothOffDrawable() {
        return mBluetoothOffDrawable;
    }

    //蓝牙控制面板弹窗所对应的布局文件
    @Override
    protected int getPanelContentLayout() {
        return R.layout.qc_bluetooth_panel;
    }

    @VisibleForTesting
    Drawable getBluetoothOnDisconnectedDrawable() {
        return mBluetoothOnDisconnectedDrawable;
    }

    @VisibleForTesting
    Drawable getBluetoothOnConnectedDrawable() {
        return mBluetoothOnConnectedDrawable;
    }
}

蓝牙状态回调接口BluetoothController.Callback:

framework/base/package/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java

public interface BluetoothController extends CallbackController<Callback>, Dumpable {
    ...代码省略...
    public interface Callback {
        void onBluetoothStateChange(boolean enabled);
        void onBluetoothDevicesChanged();
    }
}

蓝牙图标状态控制器的构造方法中初始化了蓝牙处于各种状态时所对应的图标资源,并调用BluetoothController的addCallback方法实现蓝牙状态的监听回调,
BluetoothStatusIconController最终会在回调方法onBluetoothStateChange和onBluetoothDevicesChanged方法中实时根据蓝牙的不同状态来为蓝牙图标设置对应的Drawable资源。

3、信号图标状态控制器SignalStatusIconController:

package/app/Car/SystemUI/src/com/android/systemui/car/statusicon/ui/SignalStatusIconController.java

public class SignalStatusIconController extends StatusIconController implements
        SignalCallback, HotspotController.Callback {

    private final Context mContext;
    private final Resources mResources;
    private final HotspotController mHotspotController;//热点控制器
    private final NetworkController mNetworkController;//网络控制器

    private SignalDrawable mMobileSignalIconDrawable;//手机信号Drawable
    private Drawable mWifiSignalIconDrawable;//Wifi信号Drawable
    private Drawable mHotSpotIconDrawable;//热点信号Drawable
    private boolean mIsWifiEnabledAndConnected;//Wifi是否可用可连接
    private boolean mIsHotspotEnabled;//热点是否可用

    @Inject
    SignalStatusIconController(
            Context context,
            @Main Resources resources,
            NetworkController networkController,
            HotspotController hotspotController) {
        mContext = context;
        mResources = resources;
        mHotspotController = hotspotController;
        mNetworkController = networkController;

        //为信号图标各种状态的Drawable对象进行赋值。
        mMobileSignalIconDrawable = new SignalDrawable(mContext);
        mHotSpotIconDrawable = mResources.getDrawable(R.drawable.ic_hotspot, mContext.getTheme());

        //添加回调
        mNetworkController.addCallback(this);
        mHotspotController.addCallback(this);
    }

    @Override
    protected void updateStatus() {
        if (mIsHotspotEnabled) {
            //热点信号
            setIconDrawableToDisplay(mHotSpotIconDrawable);
        } else if (mIsWifiEnabledAndConnected) {
            //Wifi信号
            setIconDrawableToDisplay(mWifiSignalIconDrawable);
        } else {
            //手机信号
            setIconDrawableToDisplay(mMobileSignalIconDrawable);
        }
        onStatusUpdated();
    }

    //SignalCallback回调方法
    @Override
    public void setMobileDataIndicators(MobileDataIndicators mobileDataIndicators) {
        mMobileSignalIconDrawable.setLevel(mobileDataIndicators.statusIcon.icon);
        updateStatus();
    }

    //SignalCallback回调方法
    @Override
    public void setWifiIndicators(WifiIndicators indicators) {
        mIsWifiEnabledAndConnected = indicators.enabled && indicators.statusIcon.visible;
        mWifiSignalIconDrawable = mResources.getDrawable(indicators.statusIcon.icon,
                mContext.getTheme());
        updateStatus();
    }

    //HotspotController.Callback的回调方法
    @Override
    public void onHotspotChanged(boolean enabled, int numDevices) {
        mIsHotspotEnabled = enabled;
        updateStatus();
    }

    //信号控制面板弹窗所对应的布局文件
    @Override
    protected int getPanelContentLayout() {
        return R.layout.qc_connectivity_panel;
    }

    @VisibleForTesting
    SignalDrawable getMobileSignalIconDrawable() {
        return mMobileSignalIconDrawable;
    }

    @VisibleForTesting
    Drawable getWifiSignalIconDrawable() {
        return mWifiSignalIconDrawable;
    }
    
    @VisibleForTesting
    Drawable getHotSpotIconDrawable() {
        return mHotSpotIconDrawable;
    }
}

热点信号回调接口HotspotController:

framework/base/package/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java

public interface HotspotController extends CallbackController<Callback>, Dumpable {
    ...代码省略...
    interface Callback {
        void onHotspotChanged(boolean enabled, int numDevices);
        default void onHotspotAvailabilityChanged(boolean available) {}
    }
}

手机信号回调接口SignalCallback:

framework/base/package/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt

interface SignalCallback {
    @JvmDefault
    fun setWifiIndicators(wifiIndicators: WifiIndicators) {}

    @JvmDefault
    fun setMobileDataIndicators(mobileDataIndicators: MobileDataIndicators) {}

    @JvmDefault
    fun setSubs(subs: List<@JvmSuppressWildcards SubscriptionInfo>) {}

    @JvmDefault
    fun setNoSims(show: Boolean, simDetected: Boolean) {}

    @JvmDefault
    fun setEthernetIndicators(icon: IconState) {}

    @JvmDefault
    fun setIsAirplaneMode(icon: IconState) {}

    @JvmDefault
    fun setMobileDataEnabled(enabled: Boolean) {}

    @JvmDefault
    fun setConnectivityStatus(
        noDefaultNetwork: Boolean,
        noValidatedNetwork: Boolean,
        noNetworksAvailable: Boolean
    ) { }

    @JvmDefault
    fun setCallIndicator(statusIcon: IconState, subId: Int) {}
}

信号图标状态控制器的构造方法中初始化了信号处于各种状态时所对应的图标资源,并分别调用调用NetworkController和HotspotController的addCallback方法实现信号状态的监听回调,SignalStatusIconController最终会在回调方法setMobileDataIndicators、setWifiIndicators和onHotspotChanged方法中实时根据信号的不同状态来为信号图标设置对应的Drawable资源。

4、信号图标状态控制器SignalStatusIconController:

package/app/Car/SystemUI/src/com/android/systemui/car/statusicon/ui/DisplayStatusIconController.java

public class DisplayStatusIconController extends StatusIconController {

    private final Drawable mDisplayBrightnessDrawable;

    @Inject
    DisplayStatusIconController(Context context, @Main Resources resources) {
        //为屏幕亮度状态图标资源Drawable对象进行赋值。
        mDisplayBrightnessDrawable = resources.getDrawable(R.drawable.car_ic_brightness,
                context.getTheme());
        updateStatus();
    }

    //更新屏幕亮度状态图标所对应的Drawable
    @Override
    protected void updateStatus() {
        setIconDrawableToDisplay(mDisplayBrightnessDrawable);
        onStatusUpdated();
    }

    //屏幕亮度控制面板弹窗所对应的布局文件
    @Override
    protected int getPanelContentLayout() {
        return R.layout.qc_display_panel;
    }
}
上一篇下一篇

猜你喜欢

热点阅读