Android12系统源码之NavigationBar和Stat

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

前言

NavigationBar 和 StatusBar 都属于 SystemBar,也叫做 decor,就是说给 App 装饰的意思。一般的 window 的布局是在 PhoneWindowManager 的 layoutWindowLw() 方法中,而 SystemBar 是在 beginLayoutLw() 方法中布局。

当前最上层的 Activity 可以修改 SystemBar 的 visibility,可以调用 View#setSystemUiVisibility() 方法,系统也有一些针对 SystemBar visibility 的策略。最终的 visibility 保存在 PhoneWindowManager 中的 mLastSystemUiFlags 变量中。

一、简单认识Android12中的DisplayFrames

mDisplayId 是跟物理屏幕相关的,DEFAULT_DISPLAY 的值是 0,mDisplayWidth 是物理屏幕宽度,mDisplayHeight 是物理屏幕高度。

public class DisplayFrames {
    public final int mDisplayId;

    public final InsetsState mInsetsState;

    /**
     * The current visible size of the screen; really; (ir)regardless of whether the status bar can
     * be hidden but not extending into the overscan area.
     */
    public final Rect mUnrestricted = new Rect();

    /**
     * During layout, the frame that is display-cutout safe, i.e. that does not intersect with it.
     */
    public final Rect mDisplayCutoutSafe = new Rect();

    public int mDisplayWidth;
    public int mDisplayHeight;

    public int mRotation;

    public DisplayFrames(int displayId, InsetsState insetsState, DisplayInfo info,
            WmDisplayCutout displayCutout, RoundedCorners roundedCorners,
            PrivacyIndicatorBounds indicatorBounds) {
        mDisplayId = displayId;
        mInsetsState = insetsState;
        onDisplayInfoUpdated(info, displayCutout, roundedCorners, indicatorBounds);
    }

    /**
     * Update {@link DisplayFrames} when {@link DisplayInfo} is updated.
     *
     * @param info the updated {@link DisplayInfo}.
     * @param displayCutout the updated {@link DisplayCutout}.
     * @param roundedCorners the updated {@link RoundedCorners}.
     * @return {@code true} if the insets state has been changed; {@code false} otherwise.
     */
    public boolean onDisplayInfoUpdated(DisplayInfo info, @NonNull WmDisplayCutout displayCutout,
            @NonNull RoundedCorners roundedCorners,
            @NonNull PrivacyIndicatorBounds indicatorBounds) {
        mRotation = info.rotation;

        final InsetsState state = mInsetsState;
        final Rect safe = mDisplayCutoutSafe;
        final DisplayCutout cutout = displayCutout.getDisplayCutout();
        if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight
                 && state.getDisplayCutout().equals(cutout)
                && state.getRoundedCorners().equals(roundedCorners)
                && state.getPrivacyIndicatorBounds().equals(indicatorBounds)) {
            return false;
        }
        mDisplayWidth = info.logicalWidth;
        mDisplayHeight = info.logicalHeight;
        final Rect unrestricted = mUnrestricted;
        unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight);
        safe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
        state.setDisplayFrame(unrestricted);
        state.setDisplayCutout(cutout);
        state.setRoundedCorners(roundedCorners);
        state.setPrivacyIndicatorBounds(indicatorBounds);
        if (!cutout.isEmpty()) {
            if (cutout.getSafeInsetLeft() > 0) {
                safe.left = unrestricted.left + cutout.getSafeInsetLeft();
            }
            if (cutout.getSafeInsetTop() > 0) {
                safe.top = unrestricted.top + cutout.getSafeInsetTop();
            }
            if (cutout.getSafeInsetRight() > 0) {
                safe.right = unrestricted.right - cutout.getSafeInsetRight();
            }
            if (cutout.getSafeInsetBottom() > 0) {
                safe.bottom = unrestricted.bottom - cutout.getSafeInsetBottom();
            }
            state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
                    unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
            state.getSource(ITYPE_TOP_DISPLAY_CUTOUT).setFrame(
                    unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
            state.getSource(ITYPE_RIGHT_DISPLAY_CUTOUT).setFrame(
                    safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
            state.getSource(ITYPE_BOTTOM_DISPLAY_CUTOUT).setFrame(
                    unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
        } else {
            state.removeSource(ITYPE_LEFT_DISPLAY_CUTOUT);
            state.removeSource(ITYPE_TOP_DISPLAY_CUTOUT);
            state.removeSource(ITYPE_RIGHT_DISPLAY_CUTOUT);
            state.removeSource(ITYPE_BOTTOM_DISPLAY_CUTOUT);
        }
        return true;
    }

    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        proto.end(token);
    }

    public void dump(String prefix, PrintWriter pw) {
        pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
                + " r=" + mRotation);
    }
}

二、NavigationBar 的布局

1、Android12系统主要是在 beginLayoutLw() 方法中对系统的SystemBar进行布局的。

 public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
        if (win == mNavigationBar && !INSETS_LAYOUT_GENERALIZATION) {
            mNavigationBarPosition = layoutNavigationBar(displayFrames,
                    mBarContentFrames.get(TYPE_NAVIGATION_BAR));
            return;
        }
        if ((win == mStatusBar && !canReceiveInput(win)) && !INSETS_LAYOUT_GENERALIZATION) {
            layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
            return;
        }
        if (win.mActivityRecord != null && win.mActivityRecord.mWaitForEnteringPinnedMode) {
            // Skip layout of the window when in transition to pip mode.
            return;
        }
        final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);

        final int type = attrs.type;
        final int fl = attrs.flags;
        final int pfl = attrs.privateFlags;
        final int sim = attrs.softInputMode;

        displayFrames = win.getDisplayFrames(displayFrames);
        final WindowFrames windowFrames = win.getLayoutingWindowFrames();

        sTmpLastParentFrame.set(windowFrames.mParentFrame);
        final Rect pf = windowFrames.mParentFrame;
        final Rect df = windowFrames.mDisplayFrame;
        windowFrames.setParentFrameWasClippedByDisplayCutout(false);

        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
        final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;

        final InsetsState state = win.getInsetsState();
        if (windowFrames.mIsSimulatingDecorWindow && INSETS_LAYOUT_GENERALIZATION) {
            // Override the bounds in window token has many side effects. Directly use the display
            // frame set for the simulated layout for this case.
            computeWindowBounds(attrs, state, df, df);
        } else {
            computeWindowBounds(attrs, state, win.getBounds(), df);
        }
        if (attached == null) {
            pf.set(df);
            if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
                final InsetsSource source = state.peekSource(ITYPE_IME);
                if (source != null) {
                    pf.inset(source.calculateInsets(pf, false /* ignoreVisibility */));
                }
            }
        } else {
            pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrame() : df);
        }

        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
        // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
        // the cutout safe zone.
        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
            final boolean attachedInParent = attached != null && !layoutInScreen;
            final boolean requestedFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR);
            final boolean requestedHideNavigation =
                    !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);

            // TYPE_BASE_APPLICATION windows are never considered floating here because they don't
            // get cropped / shifted to the displayFrame in WindowState.
            final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
                    && type != TYPE_BASE_APPLICATION;
            final Rect displayCutoutSafeExceptMaybeBars = sTmpDisplayCutoutSafeExceptMaybeBarsRect;
            displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
            if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
                if (displayFrames.mDisplayWidth < displayFrames.mDisplayHeight) {
                    displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
                    displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
                } else {
                    displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
                    displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
                }
            }

            if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
                    && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                    || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
                // At the top we have the status bar, so apps that are
                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
                // already expect that there's an inset there and we don't need to exclude
                // the window from that area.
                displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
            }
            if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
                    && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                    || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
                // Same for the navigation bar.
                switch (mNavigationBarPosition) {
                    case NAV_BAR_BOTTOM:
                        displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
                        break;
                    case NAV_BAR_RIGHT:
                        displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
                        break;
                    case NAV_BAR_LEFT:
                        displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
                        break;
                }
            }
            if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
                // The IME can always extend under the bottom cutout if the navbar is there.
                displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
            }
            // Windows that are attached to a parent and laid out in said parent already avoid
            // the cutout according to that parent and don't need to be further constrained.
            // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
            // They will later be cropped or shifted using the displayFrame in WindowState,
            // which prevents overlap with the DisplayCutout.
            if (!attachedInParent && !floatingInScreenWindow) {
                sTmpRect.set(pf);
                pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
                windowFrames.setParentFrameWasClippedByDisplayCutout(!sTmpRect.equals(pf));
            }
            // Make sure that NO_LIMITS windows clipped to the display don't extend under the
            // cutout.
            df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
        }

        // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
        // Also, we don't allow windows in multi-window mode to extend out of the screen.
        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
                && !win.inMultiWindowMode()) {
            df.left = df.top = -10000;
            df.right = df.bottom = 10000;
        }

        if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
                + ": sim=#" + Integer.toHexString(sim)
                + " attach=" + attached + " type=" + type
                + String.format(" flags=0x%08x", fl)
                + " pf=" + pf.toShortString() + " df=" + df.toShortString());

        if (!sTmpLastParentFrame.equals(pf)) {
            windowFrames.setContentChanged(true);
        }

        win.computeFrameAndUpdateSourceFrame(displayFrames);
        if (INSETS_LAYOUT_GENERALIZATION && attrs.type == TYPE_STATUS_BAR) {
            if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
                // Make sure that the zone we're avoiding for the cutout is at least as tall as the
                // status bar; otherwise fullscreen apps will end up cutting halfway into the status
                // bar.
                displayFrames.mDisplayCutoutSafe.top = Math.max(
                        displayFrames.mDisplayCutoutSafe.top,
                        windowFrames.mFrame.bottom);
            }
        }
    }
上一篇下一篇

猜你喜欢

热点阅读