Exploring the bottom layer of Android to fix the BUG (1): The Android system status bar cannot display the Wifi icon

This was my first bug when I joined the company. It gave me a headache. It was solved the next week and I spent a few more days.

The biggest reason is that my thinking is complicated. On the company system, the title of this BUG is:

Please confirm Wifi priority, status bar Wifi is ignored

BUG means: the current Android system status bar icon has a display size measurement. If, for example, 8 icons need to be displayed and cannot be drawn in the status bar, a dot will be displayed to indicate omission, and wifi is not expected to be omitted.

I thought about it and always thought it was a matter of priority. Does Android’s underlying status bar have icon priorities? When there are too many icons, the one with a higher priority will not be hidden. In fact, in the end, I referred to the company’s previous The modification of related bugs, is actually just a modification of the icon size in the status bar. If the icon is made smaller, there will be one more icon displayed in the status bar, and Wifi will not be hidden. (Click moved one position to the next icon)

Make modifications in the size file: Make these two sizes smaller (you can adjust the flash machine to take a look)

 <dimen name="status_bar_icon_size">14dip</dimen>
 <dimen name="status_bar_system_icon_size">11.5dp</dimen>

It is worth mentioning the above logic: the Java class related to the hiding, drawing, and measurement of excess icons is: StatusIconContainer.Java (although the BUG will not be changed here, record it)

/**
 * StatusIconContainer 是 StatusBarMobileView 内部的一个自定义视图容器,用于管理状态图标的显示。
 * 它负责在 StatusBarMobileView 中管理状态图标的布局和可见性。
 */
public class StatusIconContainer extends AlphaOptimizedLinearLayout {
    //TAG    
    private static final String TAG = "StatusIconContainer";
    //是否打开DEBUG模式,会Log相关信息
    private static final boolean DEBUG = false;
    //是否打开DEBUG_OVERFLOW,会在状态栏绘制边框用来显示区域什么的
    private static final boolean DEBUG_OVERFLOW = false;
    // 最多可以显示 8 个状态图标,包括电池图标
    private static final int MAX_ICONS = 7;
    //    private static final int MAX_ICONS = 8;
    //点的个数        
    private static final int MAX_DOTS = 1;
    //Dot  点  Icon图标 相关属性
    private int mDotPadding;
    private int mIconSpacing;
    private int mStaticDotDiameter;
    private int mUnderflowWidth;
    private int mUnderflowStart = 0;
    // 是否可以在溢出空间中绘制
    private boolean mNeedsUnderflow;
    // 单个 StatusBarIconView 的点图标在此宽度内居中显示
    private int mIconDotFrameWidth;
    private boolean mShouldRestrictIcons = true;
    // 用于计算哪些状态要在布局期间可见
    private ArrayList<StatusIconState> mLayoutStates = new ArrayList<>();
    // 为了正确计数和测量
    private ArrayList<View> mMeasureViews = new ArrayList<>();
    // 任何被忽略的图标将不会被添加为子视图
    private ArrayList<String> mIgnoredSlots = new ArrayList<>();

    /**
     * 创建 StatusIconContainer 的构造函数。
     *
     * @param context Android 上下文对象
     */
    public StatusIconContainer(Context context) {
        this(context, null);
    }

    /**
     * 创建 StatusIconContainer 的构造函数。
     *
     * @param context Android 上下文对象
     * @param attrs   属性集
     */
    public StatusIconContainer(Context context, AttributeSet attrs) {
        super(context, attrs);
        initDimens();
        setWillNotDraw(!DEBUG_OVERFLOW);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    }

    /**
     * 设置是否限制状态图标的显示。
     *
     * @param should 是否应该限制状态图标的显示
     */
    public void setShouldRestrictIcons(boolean should) {
        mShouldRestrictIcons = should;
    }

    /**
     * 检查是否正在限制状态图标的显示。
     *
     * @return 如果正在限制状态图标的显示,则返回 true;否则返回 false。
     */
    public boolean isRestrictingIcons() {
        return mShouldRestrictIcons;
    }

    /**
     * 初始化尺寸参数。
     */
    private void initDimens() {
        // 这是与 StatusBarIconView 使用相同的值
        mIconDotFrameWidth = getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.status_bar_icon_size);
        mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
        mIconSpacing = getResources().getDimensionPixelSize(R.dimen.status_bar_system_icon_spacing);
        // 点图标
        int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
        // 计算点的直径 R.dimen.overflow_dot_radius 2dp
        mStaticDotDiameter = 2 * radius;
        // 不能超过宽度 mIconDotFrameWidth 图标宽度 最大图标个数-1(可能还有个点,所以-1) 每个宽高
        mUnderflowWidth = mIconDotFrameWidth + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding);
        android.util.Log.d(TAG, "initDimens: " + mUnderflowStart);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        float midY = getHeight() / 2.0f;

        // 首先对所有子视图进行布局,以便稍后移动它们
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            int width = child.getMeasuredWidth();
            int height = child.getMeasuredHeight();
            int top = (int) (midY - height / 2.0f);
            child.layout(0, top, width, top + height);
        }

        resetViewStates();
        calculateIconTranslations();
        applyIconStates();
    }
//绘制
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (DEBUG_OVERFLOW) {
            //如果开启了DEBUG_OVERFLOW模式画框框
            Paint paint = new Paint();
            paint.setStyle(Style.STROKE);
            paint.setColor(Color.RED);

            // 显示边界框
            canvas.drawRect(getPaddingStart(), 0, getWidth() - getPaddingEnd(), getHeight(), paint);

            // 显示溢出框
            paint.setColor(Color.GREEN);
            canvas.drawRect(
                    mUnderflowStart, 0, mUnderflowStart + mUnderflowWidth, getHeight(), paint);
        }
    }
    //测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mMeasureViews.clear();
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int count = getChildCount();
        // 收集所有希望进行布局的视图
        for (int i = 0; i < count; i++) {
            // 获取子视图
            StatusIconDisplayable icon = (StatusIconDisplayable) getChildAt(i);
            // 如果视图可见且没有被阻止,则加入可见视图列表
            if (icon.isIconVisible() && !icon.isIconBlocked()
                    && !mIgnoredSlots.contains(icon.getSlot())) {
                mMeasureViews.add((View) icon);
            }
        }

        // 可见视图数量
        int visibleCount = mMeasureViews.size();
        int maxVisible = visibleCount <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
        int totalWidth = mPaddingLeft + mPaddingRight;
        boolean trackWidth = true;

        // 测量所有子视图,以便它们报告正确的宽度
        int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
        mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS;
        for (int i = 0; i < visibleCount; i++) {
            View child = mMeasureViews.get(i);
            measureChild(child, childWidthSpec, heightMeasureSpec);
            int spacing = i == visibleCount - 1 ? 0 : mIconSpacing;
            if (mShouldRestrictIcons) {
                if (i < maxVisible && trackWidth) {
                    totalWidth += getViewTotalMeasuredWidth(child) + spacing;
                } else if (trackWidth) {
                    // 达到图标限制;为点图标添加空间
                    totalWidth += mUnderflowWidth;
                    trackWidth = false;
                }
            } else {
                totalWidth += getViewTotalMeasuredWidth(child) + spacing;
            }
        }

        if (mode == MeasureSpec.EXACTLY) {
            if (!mNeedsUnderflow && totalWidth > width) {
                mNeedsUnderflow = true;
            }
            setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
        } else {
            if (mode == MeasureSpec.AT_MOST && totalWidth > width) {
                mNeedsUnderflow = true;
                totalWidth = width;
            }
            setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
        }
    }

    @Override
    public void onViewAdded(View child) {
        super.onViewAdded(child);
        StatusIconState vs = new StatusIconState();
        vs.justAdded = true;
        child.setTag(R.id.status_bar_view_state_tag, vs);
    }

    @Override
    public void onViewRemoved(View child) {
        super.onViewRemoved(child);
        child.setTag(R.id.status_bar_view_state_tag, null);
    }

    /**
     * 添加要忽略的图标槽的名称。它将不会显示在布局中也不会被测量。
     *
     * @param slotName 图标的名称,就像在 frameworks/base/core/res/res/values/config.xml 中定义的那样
     */
    public void addIgnoredSlot(String slotName) {
        android.util.Log.d(TAG, "addIgnoredSlot: " + slotName);
        boolean added = addIgnoredSlotInternal(slotName);
        if (added) {
            requestLayout();
        }
    }

    /**
     * 添加要忽略的图标槽的名称的列表。
     *
     * @param slots 要忽略的图标的名称列表
     */
    public void addIgnoredSlots(List<String> slots) {
        for (String slot : slots) {
            android.util.Log.d(TAG, "addIgnoredSlots: " + slot);
        }

        boolean willAddAny = false;
        for (String slot : slots) {
            willAddAny |= addIgnoredSlotInternal(slot);
        }

        if (willAddAny) {
            requestLayout();
        }
    }

    /**
     * 内部添加要忽略的图标槽名称。
     *
     * @param slotName 图标的名称,就像在 frameworks/base/core/res/res/values/config.xml 中定义的那样
     * @return 如果成功添加,则返回 true,否则返回 false
     */
    private boolean addIgnoredSlotInternal(String slotName) {
        android.util.Log.d(TAG, "addIgnoredSlotInternal: " + slotName);
        if (mIgnoredSlots.contains(slotName)) {
            return false;
        }
        mIgnoredSlots.add(slotName);
        return true;
    }

    /**
     * 从忽略的图标槽中移除一个名称。
     *
     * @param slotName 要移除的图标槽的名称
     */
    public void removeIgnoredSlot(String slotName) {
        android.util.Log.d(TAG, "removeIgnoredSlot: " + slotName);
        boolean removed = mIgnoredSlots.remove(slotName);
        if (removed) {
            requestLayout();
        }
    }

    /**
     * 从忽略的图标槽中移除名称的列表。
     *
     * @param slots 要移除的图标槽的名称列表
     */
    public void removeIgnoredSlots(List<String> slots) {
        for (String slot: slots) {
            android.util.Log.d(TAG, "removeIgnoredSlot: " + slot);
        }
        boolean removedAny = false;
        for (String slot : slots) {
            removedAny |= mIgnoredSlots.remove(slot);
        }

        if (removedAny) {
            requestLayout();
        }
    }

    /**
     * 设置要忽略的图标槽的列表,清除当前的列表。
     *
     * @param slots 要忽略的图标槽的名称列表
     */
    public void setIgnoredSlots(List<String> slots) {
        mIgnoredSlots.clear();
        addIgnoredSlots(slots);
    }

    /**
     * 返回与特定槽名称相对应的视图。
     * 仅用于操作它如何呈现。
     *
     * @param slot 槽名称,与 com.android.internal.R.config_statusBarIcons 中定义的名称相对应
     * @return 如果此容器拥有相应的视图,则返回该视图,否则返回 null
     */
    public View getViewForSlot(String slot) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child instanceof StatusIconDisplayable
                    && ((StatusIconDisplayable) child).getSlot().equals(slot)) {
                return child;
            }
        }
        return null;
    }

    /**
     * 布局从右到左发生。
     */
    private void calculateIconTranslations() {
        mLayoutStates.clear();
        float width = getWidth();
        float translationX = width - getPaddingEnd();
        float contentStart = getPaddingStart();
        int childCount = getChildCount();
        // Underflow === 不显示内容直到此索引
        if (DEBUG) Log.d(TAG, "calculateIconTranslations: start=" + translationX
                + " width=" + width + " underflow=" + mNeedsUnderflow);

        // 收集所有希望可见的状态
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            StatusIconDisplayable iconView = (StatusIconDisplayable) child;
            StatusIconState childState = getViewStateFromChild(child);

            if (!iconView.isIconVisible() || iconView.isIconBlocked()
                    || mIgnoredSlots.contains(iconView.getSlot())) {
                childState.visibleState = STATE_HIDDEN;
                if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") not visible");
                continue;
            }

            // 移动 translationX 到布局的位置,以添加视图而不截断子视图
            translationX -= getViewTotalWidth(child);
            childState.visibleState = STATE_ICON;
            childState.xTranslation = translationX;
            mLayoutStates.add(0, childState);

            // 为下一个视图移动 translationX 以腾出间隔
            translationX -= mIconSpacing;
        }

        // 显示 1 至 MAX_ICONS 个图标,或 (MAX_ICONS - 1) 个图标 + 溢出
        int totalVisible = mLayoutStates.size();
        int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;

        mUnderflowStart = 0;
        int visible = 0;
        int firstUnderflowIndex = -1;
        for (int i = totalVisible - 1; i >= 0; i--) {
            StatusIconState state = mLayoutStates.get(i);
            // 如果在测量时发现需要溢出,则允许在此之前腾出空间
            if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth)) ||
                    (mShouldRestrictIcons && visible >= maxVisible)) {
                firstUnderflowIndex = i;
                break;
            }
            mUnderflowStart = (int) Math.max(
                    contentStart, state.xTranslation - mUnderflowWidth - mIconSpacing);
            visible++;
        }
        //开始判断是否超出  超出则画点
        if (firstUnderflowIndex != -1) {
            int totalDots = 0;
            int dotWidth = mStaticDotDiameter + mDotPadding;
            int dotOffset = mUnderflowStart + mUnderflowWidth - mIconDotFrameWidth;
            for (int i = firstUnderflowIndex; i >= 0; i--) {
                StatusIconState state = mLayoutStates.get(i);
                if (totalDots < MAX_DOTS) {
                    state.xTranslation = dotOffset;
                    state.visibleState = STATE_DOT;
                    dotOffset -= dotWidth;
                    totalDots++;
                } else {
                    state.visibleState = STATE_HIDDEN;
                }
            }
        }

        // 从 NotificationIconContainer 中拿来的,不是最优解,但保持布局逻辑简洁
        if (isLayoutRtl()) {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                StatusIconState state = getViewStateFromChild(child);
                state.xTranslation = width - state.xTranslation - child.getWidth();
            }
        }
    }

    private void applyIconStates() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            StatusIconState vs = getViewStateFromChild(child);
            if (vs != null) {
                vs.applyToView(child);
            }
        }
    }

    private void resetViewStates() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            StatusIconState vs = getViewStateFromChild(child);
            if (vs == null) {
                continue;
            }

            vs.initFrom(child);
            vs.alpha = 1.0f;
            vs.hidden = false;
        }
    }

    private static @Nullable StatusIconState getViewStateFromChild(View child) {
        return (StatusIconState) child.getTag(R.id.status_bar_view_state_tag);
    }

    private static int getViewTotalMeasuredWidth(View child) {
        return child.getMeasuredWidth() + child.getPaddingStart() + child.getPaddingEnd();
    }

    private static int getViewTotalWidth(View child) {
        return child.getWidth() + child.getPaddingStart() + child.getPaddingEnd();
    }

    public static class StatusIconState extends ViewState {
        /// StatusBarIconView.STATE_*
        public int visibleState = STATE_ICON;
        public boolean justAdded = true;

        // 从视图末尾的距离是最相关的,用于动画
        float distanceToViewEnd = -1;

        @Override
        public void applyToView(View view) {
            float parentWidth = 0;
            if (view.getParent() instanceof View) {
                parentWidth = ((View) view.getParent()).getWidth();
            }

            float currentDistanceToEnd = parentWidth - xTranslation;

            if (!(view instanceof StatusIconDisplayable)) {
                return;
            }
            StatusIconDisplayable icon = (StatusIconDisplayable) view;
            AnimationProperties animationProperties = null;
            boolean animateVisibility = true;

            // 用于计算哪些状态在布局过程中是可见的,找出哪些属性的状态转换(如果有的话)我们需要动画
            // 确定要动画的状态转换的属性(如果有)
            if (justAdded
                    || icon.getVisibleState() == STATE_HIDDEN && visibleState == STATE_ICON) {
                // 图标正在出现,通过将其放在它将出现的位置并动画 alpha 来淡入它
                super.applyToView(view);
                view.setAlpha(0.f);
                icon.setVisibleState(STATE_HIDDEN);
                animationProperties = ADD_ICON_PROPERTIES;
            } else if (icon.getVisibleState() != visibleState) {
                if (icon.getVisibleState() == STATE_ICON && visibleState == STATE_HIDDEN) {
                    // 消失,不要执行任何复杂的操作
                    animateVisibility = false;
                } else {
                    // 所有其他转换(到/从点等)
                    animationProperties = ANIMATE_ALL_PROPERTIES;
                }
            } else if (visibleState != STATE_HIDDEN && distanceToViewEnd != currentDistanceToEnd) {
                // 可见性不在发生变化,只需动画位置
                animationProperties = X_ANIMATION_PROPERTIES;
            }

            icon.setVisibleState(visibleState, animateVisibility);
            if (animationProperties != null) {
                view.animate().cancel();
                icon.addTransformationToViewGroup(view, animationProperties, null);
            }
            icon.applyInShelfTransformation(view, this, animationProperties, animationProperties);
        }
    }
}

Guess you like

Origin blog.csdn.net/m0_59558544/article/details/134038994