Window size calculation

The size of the calculation window is quite different from Android 4.4. Put a little effort into relearning. In Android 4.4, the form is evaluated in onResume by calling ViewRootImpl and relayoutWindow to remeasure the size and margins of the entire Window.

What does the relayoutWindow method do? When we reach the onResume phase in the Activity lifecycle, the setView of ViewRootImpl starts the render View process and calls requestLayout to measure the render. One of the core logic is to call relayoutWindow of WMS to remeasure the Window.

Bind this process to DisplayContent in Android 9.0. Let’s dissect the method a little bit.

relayout

public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration, Surface outSurface) { int result = 0; boolean configChanged; final boolean hasStatusBarPermission = mContext.checkCallingOrSelfPermission(permission.STATUS_BAR) == PackageManager.PERMISSION_GRANTED; final boolean hasStatusBarServicePermission = mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE) == PackageManager.PERMISSION_GRANTED; long origId = Binder.clearCallingIdentity(); final int displayId; synchronized(mWindowMap) { WindowState win = windowForClientLocked(session, client, false); if (win == null) { return 0; } displayId = win.getDisplayId(); . mWindowPlacerLocked.performSurfacePlacement(true /* force */); if (shouldRelayout) { result = win.relayoutVisibleWindow(result, attrChanges, oldVisibility); try { result = createSurfaceControl(outSurface, result, win, winAnimator); } catch (Exception e) { mInputMonitor.updateInputWindowsLw(true /*force*/); Binder.restoreCallingIdentity(origId); return 0; } if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) ! = 0) { focusMayChange = isDefaultDisplay; } if (win.mAttrs.type == TYPE_INPUT_METHOD && mInputMethodWindow == null) { setInputMethodWindowLocked(win); imMayMove = true; } win.adjustStartingWindowFlags(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } else { ... } if (focusMayChange) { if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/)) { imMayMove = false; } } boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) ! = 0; final DisplayContent dc = win.getDisplayContent(); if (imMayMove) { dc.computeImeTarget(true /* updateImeTarget */); if (toBeDisplayed) { dc.assignWindowLayers(false /* setLayoutNeeded */); }}... outFrame.set(win.mCompatFrame); outOverscanInsets.set(win.mOverscanInsets); outContentInsets.set(win.mContentInsets); win.mLastRelayoutContentInsets.set(win.mContentInsets); outVisibleInsets.set(win.mVisibleInsets); outStableInsets.set(win.mStableInsets); outCutout.set(win.mDisplayCutout.getDisplayCutout()); outOutsets.set(win.mOutsets); outBackdropFrame.set(win.getBackdropFrame(win.mFrame)); result |= mInTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0; mInputMonitor.updateInputWindowsLw(true /*force*/); win.mInRelayout = false; }... Binder.restoreCallingIdentity(origId); return result; }Copy the code

Relayout basically does the following:

  • 1. Use IWindow to find the corresponding WindowState and obtain various parameters.
  • 2. WindowPlacerLocked. PerformSurfacePlacement preparation for Surface interaction.
  • 3. Create a Surface object and start interacting with SurfaceFlinger.
  • 4. UpdateFocusedWindowLocked Window change form will try to recalculate the size of the area
  • 5. Calculate the hierarchy and set the various margins of the form.

Relayout’s method is a bit long, so we’ll focus on the core logic of this section. There are two methods:

  • 1. MWindowPlacerLocked. When performSurfacePlacement form change, need to set the various parameters of DisplayContent, destroy unused Surface, recount level, calculate the contact area and so on
  • 2. UpdateFocusedWindowLocked if I found the Window may change, is to recalculate the Window size.

WindowPlacerLocked.performSurfacePlacement

final void performSurfacePlacement(boolean force) { if (mDeferDepth > 0 && ! force) { return; } int loopCount = 6; do { mTraversalScheduled = false; performSurfacePlacementLoop(); mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement); loopCount--; } while (mTraversalScheduled && loopCount > 0); mService.mRoot.mWallpaperActionPending = false; }Copy the code

Can see here face performSurfacePlacementLoop do the cycle of up to 6 times, this cycle six times what to do?

private void performSurfacePlacementLoop() { ... mInLayout = true; boolean recoveringMemory = false; if (! mService.mForceRemoves.isEmpty()) { recoveringMemory = true; while (! mService.mForceRemoves.isEmpty()) { final WindowState ws = mService.mForceRemoves.remove(0); ws.removeImmediately(); } Object tmp = new Object(); synchronized (tmp) { try { tmp.wait(250); } catch (InterruptedException e) { } } } try { mService.mRoot.performSurfacePlacement(recoveringMemory); mInLayout = false; if (mService.mRoot.isLayoutNeeded()) { if (++mLayoutRepeatCount < 6) { requestTraversal(); } else { Slog.e(TAG, "Performed 6 layouts in a row. Skipping"); mLayoutRepeatCount = 0; } } else { mLayoutRepeatCount = 0; } if (mService.mWindowsChanged && ! mService.mWindowChangeListeners.isEmpty()) { mService.mH.removeMessages(REPORT_WINDOWS_CHANGE); mService.mH.sendEmptyMessage(REPORT_WINDOWS_CHANGE); } } catch (RuntimeException e) { mInLayout = false; }}Copy the code

To see the core logic of this, we first check if there are any objects in the mForceRemoves collection under WMS. There is a call to removeImmediately to empty bindings and Surface objects between SurfaceControl and WindowContainer in WindowState, and to destroy surfaces in WindowAnimator.

This is easy because the next step will be to apply for a Surface object, and if the Android system has too much memory (OOM), the mForceRemoves object will exist and you can destroy the Surface you don’t need. This point of design and Davlik virtual machine application object when the idea is consistent.

Destruction takes a little time, so a 250-millisecond wait is required. The performSurfacePlacement of RootWindowContainer is then called to do the actual execution. Finally, events are notified to the DebugBridge debug class via handler via ViewServer.

At the end of each loop, if RootWindowContainer needs to be remeasured, the current method will be put into the Handler and wait for the next call, which is also called six times. This maximizes the Window’s ability to measure Window parameters each time during this period.

RootWindowContainer.performSurfacePlacement

Now, this is a very long method, so let’s just look at the core;

void performSurfacePlacement(boolean recoveringMemory) { int i; boolean updateInputWindowsNeeded = false; 1 if (mservice.mfocusmaychange) {mservice.mfocusmaychange = false; updateInputWindowsNeeded = mService.updateFocusedWindowLocked( UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); } // Final int numDisplays = McHildren.size (); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); displayContent.setExitingTokensHasVisible(false); } mHoldScreen = null; . final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo(); final int defaultDw = defaultInfo.logicalWidth; final int defaultDh = defaultInfo.logicalHeight; . final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked; / / 3 core events if (mService. MAppTransition. IsReady ()) {final int layoutChanges = surfacePlacer.handleAppTransitionReadyLocked(); defaultDisplay.pendingLayoutChanges |= layoutChanges; if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("after handleAppTransitionReadyLocked", defaultDisplay.pendingLayoutChanges); } if (! isAppAnimating() && mService.mAppTransition.isRunning()) { defaultDisplay.pendingLayoutChanges |= mService.handleAnimatingStoppedAndTransitionLocked(); } / / core event 4 final RecentsAnimationController RecentsAnimationController = mService. GetRecentsAnimationController (); if (recentsAnimationController ! = null) { recentsAnimationController.checkAnimationReady(mWallpaperController); } if (mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0 && ! mService.mAppTransition.isReady()) { defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT; } mWallpaperForceHidingChanged = false; . if (mService.mFocusMayChange) { mService.mFocusMayChange = false; if (mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, false /*updateInputWindows*/)) { updateInputWindowsNeeded = true; defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM; }}... final ArraySet<DisplayContent> touchExcludeRegionUpdateDisplays = handleResizingWindows(); . Boolean wallpaperDestroyed = false; i = mService.mDestroySurface.size(); if (i > 0) { do { i--; WindowState win = mService.mDestroySurface.get(i); win.mDestroying = false; if (mService.mInputMethodWindow == win) { mService.setInputMethodWindowLocked(null); } if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) { wallpaperDestroyed = true; } win.destroySurfaceUnchecked(); win.mWinAnimator.destroyPreservedSurfaceLocked(); } while (i > 0); mService.mDestroySurface.clear(); }... / / core event 6 mService. MInputMonitor. UpdateInputWindowsLw force (true / * * /); mService.setHoldScreenLocked(mHoldScreen); . if (mSustainedPerformanceModeCurrent ! = mSustainedPerformanceModeEnabled) { mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent; mService.mPowerManagerInternal.powerHint( PowerHint.SUSTAINED_PERFORMANCE, (mSustainedPerformanceModeEnabled ? 1:0)); }... / / events 7 final int N = mService. MPendingRemove. The size (); if (N > 0) { if (mService.mPendingRemoveTmp.length < N) { mService.mPendingRemoveTmp = new WindowState[N+10]; } mService.mPendingRemove.toArray(mService.mPendingRemoveTmp); mService.mPendingRemove.clear(); ArrayList<DisplayContent> displayList = new ArrayList(); for (i = 0; i < N; i++) { final WindowState w = mService.mPendingRemoveTmp[i]; w.removeImmediately(); final DisplayContent displayContent = w.getDisplayContent(); if (displayContent ! = null && ! displayList.contains(displayContent)) { displayList.add(displayContent); } } for (int j = displayList.size() - 1; j >= 0; --j) { final DisplayContent dc = displayList.get(j); dc.assignWindowLayers(true /*setLayoutNeeded*/); }} for (int displayNdx = McHildren.size () -1; displayNdx >= 0; --displayNdx) { mChildren.get(displayNdx).checkCompleteDeferredRemoval(); } if (updateInputWindowsNeeded) { mService.mInputMonitor.updateInputWindowsLw(false /*force*/); } mService.setFocusTaskRegionLocked(null); if (touchExcludeRegionUpdateDisplays ! = null) { final DisplayContent focusedDc = mService.mFocusedApp ! = null ? mService.mFocusedApp.getDisplayContent() : null; for (DisplayContent dc : touchExcludeRegionUpdateDisplays) { if (focusedDc ! = dc) { dc.setTouchExcludeRegion(null /* focusedTask */); }}} / / core event 9 mService enableScreenIfNeededLocked (); mService.scheduleAnimationLocked(); }Copy the code

I’ve divided it into 9 sections:

  • 1. If the WMS found that the change in the focus of the current Window, will call updateFocusedWindowLocked to measure the Window size.
  • 2. Set all upcoming Windows Token to invisible flag bits.
  • 3. Check that all Windows are visible when executing App Transition. Then we need to animate our Windows naturally, without actually drawing the view to the screen, so we have to postpone a lot of things in order to do this. Displaying hidden programs, arranging forms along the Z-axis, and so on, requires the re-establishment of the Window hierarchy.
  • 4. Postpone wallpaper painting.
  • 5. Remove the Surface to be destroyed
  • 6. Update the range of contact events
  • 7. Clear the surfaces that need to be put off when the animation is over, and then rearrange the hierarchy
  • 8. Update the contact event policy, and update the contact event scope (there will be a special input system column to talk about this)
  • 9. Perform form animation

This is just for the overview, and we’ll go in for details when we have a chance.

WMS.updateFocusedWindowLocked

Where we can see whether, if the window has changed, will call updateFocusedWindowLocked method. In fact, this method is really the core of the measurement window size logic.

boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { WindowState newFocus = mRoot.computeFocusedWindow(); if (mCurrentFocus ! = newFocus) { ... final DisplayContent displayContent = getDefaultDisplayContentLocked(); boolean imWindowChanged = false; if (mInputMethodWindow ! = null) { final WindowState prevTarget = mInputMethodTarget; final WindowState newTarget = displayContent.computeImeTarget(true /* updateImeTarget*/); imWindowChanged = prevTarget ! = newTarget; if (mode ! = UPDATE_FOCUS_WILL_ASSIGN_LAYERS && mode ! = UPDATE_FOCUS_WILL_PLACE_SURFACES) { final int prevImeAnimLayer = mInputMethodWindow.mWinAnimator.mAnimLayer; displayContent.assignWindowLayers(false /* setLayoutNeeded */); imWindowChanged |= prevImeAnimLayer ! = mInputMethodWindow.mWinAnimator.mAnimLayer; } } if (imWindowChanged) { mWindowsChanged = true; displayContent.setLayoutNeeded(); newFocus = mRoot.computeFocusedWindow(); }... int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus); if (imWindowChanged && oldFocus ! = mInputMethodWindow) { if (mode == UPDATE_FOCUS_PLACING_SURFACES) { displayContent.performLayout(true /*initial*/, updateInputWindows); focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT; } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) { displayContent.assignWindowLayers(false /* setLayoutNeeded */); } } if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) ! = 0) { displayContent.setLayoutNeeded(); if (mode == UPDATE_FOCUS_PLACING_SURFACES) { displayContent.performLayout(true /*initial*/, updateInputWindows); } } if (mode ! = UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows); }... return true; } return false; }Copy the code

Note that isWindowChange determines whether the input method focus is consistent, while window focus is determined by different WindowStates.

  • 1. Recalculate the hierarchy if the input method popover is found, or if the input method focus window changes, find the current focus window from RootWindowContainer.
  • 2. Recalculate the form size if the focus of the input method changes and the current mode is UPDATE_FOCUS_PLACING_SURFACES(requiring a forced redraw), otherwise simply make a hierarchy change.
    1. Typically, when UPDATE_FOCUS_PLACING_SURFACES is used, performLayout remeasures the margins of the form
  • 4. If the UPDATE_FOCUS_WILL_ASSIGN_LAYERS mode is not used, contact focus margins need to be processed.

Actually core measure the real action is DisplayContent performLayout. When we think about it, in Android 9.0, DisplayContent stood for a logical screen, and when we talk about a split screen, we actually mean the size of our current form that covers the various margins of the logical screen.

Type of form margin

Before we start talking about Window size measurements, in fact, in Android, in order to mark the boundaries of the Window, there are actually more and more margin types that can be used to measure the size of the Window as The Times and aesthetic trends evolve.

In DisplayFrame we have the approximate partition as follows:

type describe
mOverScan Margin names with this prefix and suffix represent overscanned. What is a scan? In fact, when we look at our own phone screen, we find that the display area of the phone is not fully covered, but has a little black border. And this black area here is the over-scanned area. The reason is that if the display range spread out on the full screen, such as television screens will lead to distortion.
mOverScanScreen Represents the true width and height of the display screen, which is carried over the scan margin range.
mRestrictedOverscan Similar to OverScanScreen, move to OverScanScreen is allowed when appropriate
mUnrestricted True screen size, but without the OverScan area
mRestricted Current screen size; If the status bar cannot be hidden, these values may differ from (0,0)-(dw,dh); In this case, it effectively separates the display area from all other Windows.
mSystem During layout, all areas of SysytemUI elements are visible
mStable A stable application content area
mStableFullscreen For a stable application area, but the Window is tagged with the FullScreen flag, which is the area in addition to the StatusBar
mCurrent During layout, the area of the current screen with the keyboard, status bar (although difficult to understand, but easy to understand if it is split screen and free window mode)
mContent During layout, all areas that present content to the user, including all external decorations such as state and keyboard, are generally the same as mCurrent, but larger than mCurrent unless nested mode is used.
mVoiceContent During layout, the area of the system where our sound volume changes
mDock Input method form area
mDisplayCutout The area above the fringe
mDisplayCutoutSafe Bangs do not allow the use of cross sections

As you can see, the margins on these forms actually follow the trend of these years. Like Android 7.0’s free form mode, nested form mode, bangs, etc., these margins work together to create a true Window size. With that in mind, let’s look at the logic of measuring size.

DisplayContent.performLayout

void performLayout(boolean initial, boolean updateInputWindows) { if (! isLayoutNeeded()) { return; } clearLayoutNeeded(); final int dw = mDisplayInfo.logicalWidth; final int dh = mDisplayInfo.logicalHeight; mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation)); mDisplayFrames.mRotation = mRotation; mService.mPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode); if (isDefaultDisplay) { mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw(); mService.mScreenRect.set(0, 0, dw, dh); } int seq = mLayoutSeq + 1; if (seq < 0) seq = 0; mLayoutSeq = seq; mTmpWindow = null; mTmpInitial = initial; ForAllWindows (mPerformLayout, true /* traverseTopToBottom */); . // Measure those Windows that are bound to the parent window forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */); . }Copy the code

Here we break this method down into the following parts:

  • Set the DisplayFrame
  • BeginLayoutLw Begins measurement
  • Measure Windows that are not bound to a parent window
  • Measure the Windows that are bound to the parent window

Set the DisplayFrame

public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout) { mDisplayWidth = info.logicalWidth; mDisplayHeight = info.logicalHeight; mRotation = info.rotation; mDisplayInfoOverscan.set( info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom); mDisplayInfoCutout = displayCutout ! = null ? displayCutout : WmDisplayCutout.NO_CUTOUT; }Copy the code

Can see, at this time will set the size of the current display screen, and obtain the scan area, but also determine whether the current phone screen support bangs. All of this is actually information that the hardware feeds back to the DisplayService that we get from it.

PhoneWindowManager. BeginLayoutLw began to measure the size and margin

In fact, those of you who are paying attention to my first WMS article will see that when WMS initializes, we can see that WMS initializes a WindowManagerPolicy policy, and that policy is PhoneWindowManager. In fact, it also allows the system to develop custom policies to calculate the results it wants on the form.

public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) { displayFrames.onBeginLayout(); mSystemGestures.screenWidth = displayFrames.mUnrestricted.width(); mSystemGestures.screenHeight = displayFrames.mUnrestricted.height(); mDockLayer = 0x10000000; mStatusBarLayer = -1; / / the start with the current dock the rect, which will be (0, 0, displayWidth displayHeight) final the rect pf = mTmpParentFrame; // Parent window size final Rect df = mTmpDisplayFrame; // Final Rect of = mTmpOverscanFrame; Final Rect vf = mTmpVisibleFrame; Final Rect DCF = mTmpDecorFrame; // Input method size vf.set(displayFrames. MDock); of.set(displayFrames.mDock); df.set(displayFrames.mDock); pf.set(displayFrames.mDock); dcf.setEmpty(); // Decor frame N/A for system bars. if (displayFrames.mDisplayId == DEFAULT_DISPLAY) { final int sysui = mLastSystemUiFlags; boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; boolean navTranslucent = (sysui & (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) ! = 0; boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) ! = 0; boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) ! = 0; boolean navAllowedHidden = immersive || immersiveSticky; navTranslucent &= ! immersiveSticky; // transient trumps translucent boolean isKeyguardShowing = isStatusBarKeyguard() && ! mKeyguardOccluded; if (! isKeyguardShowing) { navTranslucent &= areTranslucentBarsAllowed(); } boolean statusBarExpandedNotKeyguard = ! isKeyguardShowing && mStatusBar ! = null && mStatusBar.getAttrs().height == MATCH_PARENT && mStatusBar.getAttrs().width == MATCH_PARENT; . navVisible |= ! canHideNavigationBar(); boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, dcf, navVisible, navTranslucent, navAllowedHidden, statusBarExpandedNotKeyguard); updateSysUiVisibility |= layoutStatusBar( displayFrames, pf, df, of, vf, dcf, sysui, isKeyguardShowing); if (updateSysUiVisibility) { updateSystemUiVisibilityLw(); } } layoutScreenDecorWindows(displayFrames, pf, df, dcf); if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) { displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top, displayFrames.mStable.top); }}Copy the code

First initialize several parameters, parent form, screen, over scan, visible area, input method area for the size of the current logical display, and then do clipping.

You can see all the things that are really focused on the system UI judgment, checking NavBar, StatusBar size. Finally judge the current bangs screen does not allow cross area top and display top which big. If the top of mDisplayCutoutSafe is greater than the top of mUnrestricted, then mDisplayCutoutSafe is under mUnrestricted, which is the area above me that contains a black section. The stable application area and the maximum value at the top of the bangs area are taken as the bangs screen area. This ensures that the top of the bangs is the status bar.

Note that if the NavigationBar is hidden, a dummy area is created to capture all input events.

There are four key functions:

  • The onBeginLayout function initializes all margin values
  • LayoutNavigationBar, measure NavigationBar
  • LayoutStatusBar measuring layoutStatusBar
  • LayoutScreenDecorWindows measures all decorated Windows

DisplayFrame.onBeginLayout

public void onBeginLayout() { switch (mRotation) { case ROTATION_90: ... break; case ROTATION_180: ... break; case ROTATION_270: ... break; default: mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan); break; } mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight); mOverscan.set(mRestrictedOverscan); mSystem.set(mRestrictedOverscan); mUnrestricted.set(mRotatedDisplayInfoOverscan); mUnrestricted.right = mDisplayWidth - mUnrestricted.right; mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom; mRestricted.set(mUnrestricted); mDock.set(mUnrestricted); mContent.set(mUnrestricted); mVoiceContent.set(mUnrestricted); mStable.set(mUnrestricted); mStableFullscreen.set(mUnrestricted); mCurrent.set(mUnrestricted); mDisplayCutout = mDisplayInfoCutout; mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); if (! mDisplayCutout.getDisplayCutout().isEmpty()) { final DisplayCutout c = mDisplayCutout.getDisplayCutout(); if (c.getSafeInsetLeft() > 0) { mDisplayCutoutSafe.left = mRestrictedOverscan.left + c.getSafeInsetLeft(); } if (c.getSafeInsetTop() > 0) { mDisplayCutoutSafe.top = mRestrictedOverscan.top + c.getSafeInsetTop(); } if (c.getSafeInsetRight() > 0) { mDisplayCutoutSafe.right = mRestrictedOverscan.right - c.getSafeInsetRight(); } if (c.getSafeInsetBottom() > 0) { mDisplayCutoutSafe.bottom = mRestrictedOverscan.bottom - c.getSafeInsetBottom(); }}}Copy the code

You can see that all of the spacing will be set to the initial width and height of the mUnrestricted, that is, not including the OverScan area. If a fringe screen is encountered, the mDisplayCutoutSafe security zone is set based on the SafeInset zone. That’s what I did up there. For example, if LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT is set, the display area will not go beyond the bottom of the bangs.

NavigationBar layoutNavigationBar measurement

private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, Rect dcf, boolean navVisible, boolean navTranslucent, boolean navAllowedHidden, boolean statusBarExpandedNotKeyguard) { if (mNavigationBar == null) { return false; } boolean transientNavBarShowing = mNavigationBarController.isTransientShowing(); final int rotation = displayFrames.mRotation; final int displayHeight = displayFrames.mDisplayHeight; final int displayWidth = displayFrames.mDisplayWidth; final Rect dockFrame = displayFrames.mDock; mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation); final Rect cutoutSafeUnrestricted = mTmpRect; cutoutSafeUnrestricted.set(displayFrames.mUnrestricted); cutoutSafeUnrestricted.intersectUnchecked(displayFrames.mDisplayCutoutSafe); if (mNavigationBarPosition == NAV_BAR_BOTTOM) { final int top = cutoutSafeUnrestricted.bottom - getNavigationBarHeight(rotation, uiMode); mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom); displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top; . if (navVisible && ! navTranslucent && ! navAllowedHidden && ! mNavigationBar.isAnimatingLw() && ! mNavigationBarController.wasRecentlyTranslucent()) { displayFrames.mSystem.bottom = top; }} // Do not care about post-rotation measurements.... displayFrames.mCurrent.set(dockFrame); displayFrames.mVoiceContent.set(dockFrame); displayFrames.mContent.set(dockFrame); mStatusBarLayer = mNavigationBar.getSurfaceLayer(); mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe, mTmpNavigationFrame, dcf, mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe, displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */); mNavigationBarController.setContentFrame(mNavigationBar.getContentFrameLw()); return mNavigationBarController.checkHiddenLw(); }Copy the code

We pay attention to the assignment of the mTmpNavigationFrame object. In normal cases, the range is as follows:

Left: 0 top: Navigation height of the bangs screen MUnrestricted (does not include the bottom of the scanned area)

At this point, the bottom of the mStable and mStableFullscreen areas are corresponding to the top, that is, the top of Navigation. The bottom of the System element is also the top of the Navigation.

Finally, the value of this region is recalculated through computeFrameLw. I’ll talk about this later, but in normal mobile development, it doesn’t really change. For mNavigationBar:

Visible region, scanning area, content area, the Dock area: mTmpNavigationFrame scanning area, stable area, bang cutting zone: displayFrames. MDisplayCutoutSafe

LayoutStatusBar measuring layoutStatusBar

    private boolean layoutStatusBar(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect vf,
            Rect dcf, int sysui, boolean isKeyguardShowing) {
        // decide where the status bar goes ahead of time
        if (mStatusBar == null) {
            return false;
        }
        of.set(displayFrames.mUnrestricted);
        df.set(displayFrames.mUnrestricted);
        pf.set(displayFrames.mUnrestricted);
        vf.set(displayFrames.mStable);

        mStatusBarLayer = mStatusBar.getSurfaceLayer();
size.
        mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
                vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
                dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */,
                displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);

        displayFrames.mStable.top = displayFrames.mUnrestricted.top
                + mStatusBarHeightForRotation[displayFrames.mRotation];
        displayFrames.mStable.top = Math.max(displayFrames.mStable.top,
                displayFrames.mDisplayCutoutSafe.top);

...
        mStatusBarController.setContentFrame(mTmpRect);

...
        if (mStatusBar.isVisibleLw() && !statusBarTransient) {
            final Rect dockFrame = displayFrames.mDock;
            dockFrame.top = displayFrames.mStable.top;
            displayFrames.mContent.set(dockFrame);
            displayFrames.mVoiceContent.set(dockFrame);
            displayFrames.mCurrent.set(dockFrame);


            if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
                    && !mStatusBarController.wasRecentlyTranslucent()) {
                displayFrames.mSystem.top = displayFrames.mStable.top;
            }
        }
        return mStatusBarController.checkHiddenLw();
    }
Copy the code

Similarly for statusBar:

The Father area, display area, over-scan area, content area, visible area, stable area and external area are all mUnrestricted Dock area is 0

Note that if statusBar is visible, the following calculation is done:

The top of the displayFrames. MStable moves down the height of the StatusBar and then determines which is closer to the bottom than the current security clipping. This ensures that our app stays below StatusBar and can be securely clipped with bangs.

If Statusbar is visible, and of course dockFrame, the whole thing should also be removed from the top of the screen across the scanned area and moved down the Statusbar height. The same is true of the system elements represented by mSystem. This ensures that no one blocks the system status bar.

It’s quite common for us to jump from a page with a hidden status bar to a page with a status bar, and there’s a PopupWindow, and you can see that popWindow is obviously moving down.

layoutScreenDecorWindows

private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) { if (mScreenDecorWindows.isEmpty()) { return; } final int displayId = displayFrames.mDisplayId; final Rect dockFrame = displayFrames.mDock; final int displayHeight = displayFrames.mDisplayHeight; final int displayWidth = displayFrames.mDisplayWidth; for (int i = mScreenDecorWindows.size() - 1; i >= 0; --i) { final WindowState w = mScreenDecorWindows.valueAt(i); if (w.getDisplayId() ! = displayId || ! w.isVisibleLw()) { // Skip if not on the same display or not visible. continue; } w.computeFrameLw(pf /* parentFrame */, df /* displayFrame */, df /* overlayFrame */, df /* contentFrame */, df /* visibleFrame */, dcf /* decorFrame */, df /* stableFrame */, df /* outsetFrame */, displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */); final Rect frame = w.getFrameLw(); if (frame.left <= 0 && frame.top <= 0) { // Docked at left or top. if (frame.bottom >= displayHeight) { // Docked left. dockFrame.left = Math.max(frame.right, dockFrame.left); } else if (frame.right >= displayWidth ) { // Docked top. dockFrame.top = Math.max(frame.bottom, dockFrame.top); } else { } } else if (frame.right >= displayWidth && frame.bottom >= displayHeight) { // Docked at right or bottom. if (frame.top <= 0) { // Docked right. dockFrame.right = Math.min(frame.left, dockFrame.right); } else if (frame.left <= 0) { // Docked bottom. dockFrame.bottom = Math.min(frame.top, dockFrame.bottom); } else { } } else { } } displayFrames.mRestricted.set(dockFrame); displayFrames.mCurrent.set(dockFrame); displayFrames.mVoiceContent.set(dockFrame); displayFrames.mSystem.set(dockFrame); displayFrames.mContent.set(dockFrame); displayFrames.mRestrictedOverscan.set(dockFrame); }Copy the code

In this method, the mScreenDecorWindows collection is actually added in adjustWindowParamsLw and prepareAddWindowLw. The added condition is that whenever a new Window is added (WMS addView) or the Window needs to be reconfigured (WMS relayoutWindow), the newly added Window or the Window that needs to relayout has the StatusBar permission. And the display is added to the mScreenDecorWindows collection.

MScreenDecorWindows can tell from the above description that this step is not actually hierarchical. But it doesn’t matter, these are just preliminary measurements.

With mScreenDecorWindows in mind, it’s easy to read the above method.

Layoutscreendecordecorwindows does exactly what it’s called, measuring Window decorators such as StatusBar and input methods. At this point, after a loop, the computeFrameLw of all WindowStates is called from the tail to the head to calculate the Window size corresponding to each WindowState.

After calculating the size of each form, the event is divided into two cases where the left and top of the current Window are both less than or equal to 0, that is, the top edge of the current Window and the left edge exceeds the current screen.

It shows that there is something on the bottom right that holds the whole Window up. So the dockFrame calculation is simple:

  • When the bottom of the current Frame is greater than or equal to the height of the screen, there is probably nothing at the bottom, and the dockFrame is on the right. Calculate the leftmost position (dockframe. left) which is on the right of the current window compared to the previous WindowState.
  • When the right side of the current Frame is greater than or equal to the screen width, there may be nothing on the right side and the dockFrame is at the bottom. Just calculate the top of the dockFrame (dockframe.top) and the bottom of the frame which is larger (who is closer to the bottom).

If bottom is greater than or equal to the screen height and right is greater than or equal to the screen width. It means there’s something on the top left pushing the whole Window down.

  • When the top of the current Frame is less than or equal to 0, there is nothing resting on top and the dock is on the left. Calculate the right side of the original dockFrame (dockframe.right) and the left side of the current Frame.
  • When the left side of the current Frame is less than or equal to 0, there is nothing on the left and the dock is at the top. You only need to calculate the bottom of the dockFrame (dockframe.bottom) and the top of the current frame which is smaller (near the top).

And then finally I’m going to set this and I’m going to set all the visible areas of the displayFrames and so on to dockFrame. The federated context, in effect, moves the top of the entire area below the statusBar.

WindowState.computeFrameLw

The method is very long, so I’ve just captured the points that need to be noticed and added comments. Here’s a quick summary:

MFrame has a value only in free form mode and fixed mode. Otherwise it’s all (0,0,0,0) and this mFrame is confusing me and I get it all at once. In fact, mFrame was created to ensure minimal content values in free form mode, which can change just like forms on a PC. In the general case, there is no need to set this value, since fields such as mContentFrame will determine the size of the form.

Then we have the following formula to calculate the window Frame

mOverscanFrame = OverscanFrame

mContentFrame = Min(mContentFrame, mFrame)

mVisibleFrame = Min(mVisibleFrame,mFrame)

mDecorFrame = mTmpDecorFrame

mStableFrame = Min(mStableFrame,mFrame)

When in full screen:

layoutContainingFrame = parentFrame

Not full screen:

layoutContainingFrame = ! mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;

MInsetFrame This is a temporary Frame, a Frame for animation. No animation is actually mContainingFrame

Evaluate the form’s Insets

mOverscanInsets = Max(mOverscanFrame-layoutContainingFrame,0)

mContentInsets = mContentFrame – mFrame

mVisibleInsets = mVisibleFrame – mFrame

mStableInsets = mStableFrame – mFrame

When we look at this, we can see that a Frame in a Window is actually the total region inside, and Inset is the real region. But I’m just going to do the easy part here. Are based on the width and height of the display screen to do a simple difference calculation.

Measure Windows that are not bound to parent Windows (all root Windows)

Core logic:

 forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
Copy the code

In fact, forAllWindow is all the forms in the DisplayContent loop. We just need to look at the interface of this implementation:

private final Consumer<WindowState> mPerformLayout = w -> { final boolean gone = (mTmpWindow ! = null && mService.mPolicy.canBeHiddenByKeyguardLw(w)) || w.isGoneForLayoutLw(); . if (! gone || ! w.mHaveFrame || w.mLayoutNeeded || ((w.isConfigChanged() || w.setReportResizeHints()) && ! w.isGoneForLayoutLw() && ((w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) ! = 0 || (w.mHasSurface && w.mAppToken ! = null && w.mAppToken.layoutConfigChanges)))) { if (! w.mLayoutAttached) { w.mLayoutNeeded = false; w.prelayout(); final boolean firstLayout = ! w.isLaidOut(); mService.mPolicy.layoutWindowLw(w, null, mDisplayFrames); . }}};Copy the code

Once it is determined that it is visible and, the PhoneWindowManager layoutWindowLw is called.

PhoneWindowManager.layoutWindowLw

This method is more verbose, here are two parts to talk about:

  • 1. Set the upper, lower, and left edges of the area according to type
  • 2. Set the area according to other signs
Sets the top, bottom, left, and right edges of the region based on type

This does not focus on the post-rotation and status bar drop-down window and other Windows, refers to our common Activity window. We can roughly be divided into the following conditions for measurement:

  • The input method
  • The status bar
  • wallpaper
  • The sound window
  • Status bar drop-down window
  • Others (such as an Activity, a PhoneWindow corresponding to a Dialog, or a launch window, etc.). Content window.

This article focuses only on input methods, status bars, and content Windows.

The input method

Input method area = bottom of mDock Need to be adjusted: bottom of input method over-scan area, bottom of display area, bottom of parent area = mUnrestricted. Bottom Content area, visible area = mStable. Bottom The center of the entire input method is at the bottom of the entire form

The status bar

Status bar parent area, display area, over-scan area = mUnrestricted Visible area, content area = mStable

Adjust_resize = SOFT_INPUT_ADJUST_RESIZE

Bottom of the content area = McOntent.bottom

Close the SOFT_INPUT_ADJUST_RESIZE:

Bottom of the content area = mdock. bottom Bottom of the visible area = McOntent.bottom

When SOFT_INPUT_ADJUST_RESIZE is turned off, for example when adjustPan is turned on, the whole screen moves up. The content area is now the bottom of the keyboard, and the visible area is of course the bottom of the original content area.

While the status bar is consistently at the top, what it actually shows is just the tip of the iceberg, with the rest obscured by content pop-ups. For rigor, the bottom of the status bar also needs to be changed to accommodate the height change of the window.

When SOFT_INPUT_ADJUST_RESIZE is turned on, it makes space for the keyboard to adjust the Activity content, so the bottom is still the bottom of the content area.

Content window
  • First adjust the DecorFrame to decorate the area (external Window mount area such as statusBar)

DecorFrame = mSystem

If this is the window of App application: when fitSystemWindows is opened, the following flag bit is not opened:

  • SYSTEM_UI_FLAG_FULLSCREEN
  • FLAG_FULLSCREEN
  • FLAG_TRANSLUCENT_STATUS
  • FLAG_DRAWS_SYSTEM_BAR_BACKGROUND
  • PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND is actually a common Activity, Dialog.

DecorFrame.top = mStable.top

If Nav Bar is hidden:

DecorFrame.left = mStable.bottom;

DecorFrame.right = mStable.right;

Then adjust the OverScanFrame, displayFrame, parentFrame, and finally adjust the ContentFrame and VisiblityFrame.

If both the flag bit layoutInScreen and layoutInsetDecor are on and there is no binding window: if FLAG_LAYOUT_IN_OVERSCAN is on:

Over scan, display area, parent area = mOverScan

If you hide NavBar:

Display, parent area = mOverscan; MUnrestricted ensures that no window exceeds the navigation bar and that no content is inserted into the scanned area

Other information:

Display area, parent area = mRestrictedOverscan; Scanned area = mUnrestricted ensures that no content is inserted into the scanned area

If full screen is turned off:

SOFT_INPUT_ADJUST_RESIZE: Content area = mDock SOFT_INPUT_ADJUST_RESIZE: Content area = mContent

If full screen is turned on:

Content area = mRestricted

And mStable and mStableFullScreen, get the big side.

AdjustNothing closed:

Visible region = mCurrent

AdjustNothing open:

Visible area = content area just measured

So this state can handle PopWindow moving because of the status bar and so on.

If only the flag layoutInScreen is open, the Nav Bar is hidden and no binding window is available: this is very similar to the above, in that the content area is set for multiple synchronization based on the flag each time. But the visible area is still handled by adjustNothing.

That is, this state will do no more to the full screen.

When turning off SOFT_INPUT_ADJUST_RESIZE

Display area, parent area, Content area = mDock

When open SOFT_INPUT_ADJUST_RESIZE:

Display area, parent area, content area = mContent

Close the SOFT_INPUT_ADJUST_NOTHING:

Visible region = mCurrent

AdjustNothing open:

Visible area = content area just measured

According to the other flags, set the region last

boolean parentFrameWasClippedByDisplayCutout = false; final int cutoutMode = attrs.layoutInDisplayCutoutMode; final boolean attachedInParent = attached ! = null && ! layoutInScreen; final boolean requestedHideNavigation = (requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) ! = 0; final boolean floatingInScreenWindow = ! attrs.isFullscreen() && layoutInScreen && type ! = TYPE_BASE_APPLICATION; if (cutoutMode ! = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { final Rect displayCutoutSafeExceptMaybeBars = mTmpDisplayCutoutSafeExceptMaybeBarsRect; displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe); if (layoutInScreen && layoutInsetDecor && ! requestedFullscreen && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) { displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE; } if (layoutInScreen && layoutInsetDecor && ! requestedHideNavigation && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) { switch (mNavigationBarPosition) { case  NAV_BAR_BOTTOM: displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; break; case NAV_BAR_RIGHT: ... break; case NAV_BAR_LEFT: ... break; } } if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) { displayCutoutSafeExceptMaybeBars.bottom  = Integer.MAX_VALUE; } if (! attachedInParent && ! floatingInScreenWindow) { mTmpRect.set(pf); pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars); parentFrameWasClippedByDisplayCutout |= ! mTmpRect.equals(pf); } df.intersectUnchecked(displayCutoutSafeExceptMaybeBars); } cf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); . win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf, displayFrames.mDisplayCutout, parentFrameWasClippedByDisplayCutout); if (type == TYPE_INPUT_METHOD && win.isVisibleLw() && ! win.getGivenInsetsPendingLw()) { setLastInputMethodWindowLw(null, null); offsetInputMethodWindowLw(win, displayFrames); } if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw() && ! win.getGivenInsetsPendingLw()) { offsetVoiceInputWindowLw(win, displayFrames); }}Copy the code

In fact, this section is based on the bangs screen processing interval, and finally call computeFrameLw to set the region. The following logic has been discussed above.

Measure the Windows that are bound to the parent window

The attach logic is similar to the attach logic:

private void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached, boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf, DisplayFrames displayFrames) { if (! win.isInputMethodTarget() && attached.isInputMethodTarget()) { vf.set(displayFrames.mDock); cf.set(displayFrames.mDock); of.set(displayFrames.mDock); df.set(displayFrames.mDock); } else { if (adjust ! = SOFT_INPUT_ADJUST_RESIZE) { cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) ! = 0? attached.getContentFrameLw() : attached.getOverscanFrameLw()); } else { cf.set(attached.getContentFrameLw()); if (attached.isVoiceInteraction()) { cf.intersectUnchecked(displayFrames.mVoiceContent); } else if (win.isInputMethodTarget() || attached.isInputMethodTarget()) { cf.intersectUnchecked(displayFrames.mContent);  } } df.set(insetDecors ? attached.getDisplayFrameLw() : cf); of.set(insetDecors ? attached.getOverscanFrameLw() : cf); vf.set(attached.getVisibleFrameLw()); } pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df); }Copy the code

If the attached form is an input method, everything is limited by the input method. Close the SOFT_INPUT_ADJUST_RESIZE:

FLAG_LAYOUT_ATTACHED_IN_DECOR Whether to enable it? Closed content area = Parent form content area Open Content area = parent form scan area

Open SOFT_INPUT_ADJUST_RESIZE:

Gets which of the comparison subcontent areas or mContent is larger

Display, overscan, Visible area = Parent (restricted by parent form)

conclusion

BeginLayoutLw, layoutWindowLw Determines the size of the Window by determining the margin of the entire Window. BeginLayoutLw, does the following:

  • Measure the Nav Bar
  • Measuring the status Bar
  • Measuring layoutDecorScreen

A preliminary measurement of the entire screen was made, limiting the remaining Windows to the statusBar. No form is allowed to block it.

LayoutWindowLw does the following: measures the display area, overscan area, parent area, content area, and visible area of all forms. What can be noticed is that there are two logos for layoutInScreen and layoutInDecor. These two flag bits determine the maximum range the window can move.

Content area is determined by adjustResize marker bit. If open, it is the scope of the content area. Because this flag handles the Activity adjust the space to make room for the keyboard. If layoutInScreen is turned on, the content area is mDock; otherwise, the content area is mContent, or go according to the flag.

AdjustNothing (adjustNothing) adjustNothing (adjustNothing) adjustNothing (mCurrent) adjustNothing (adjustNothing)