Android R DisplayManagerService(6) Logical Display和Physical Display配置的更新

4,008 阅读8分钟

Android R DisplayManagerService模块(1) 启动中说过,系统中每个物理屏都对应一个逻辑屏,DisplayInfo代表逻辑屏封装的信息对象,它并非固定不变的,当DisplayInfo对象发生变化后,WMS模块就会向DMS中发送通知,告知DMS模块进行相应处理。这篇文章就对逻辑屏信息发生变化后,DMS中的更新流程进行总结。

DMS中逻辑屏和物理屏配置参数的更新过程,和WMS模块有着密切的联系,因为DisplayInfo的更新发生在WMS中,当在更新Configuration时,会对DisplayInfo进行更新,如果发现发生了变化,如屏幕方向、屏幕大小、屏幕密度等属性发生变化,WMS会向DMS设置新的DisplayInfo。同时DMS中状态发生变化时,也会向WMS发出遍历请求,根据WMS中窗口状态设置对应的配置。

1.DisplayContent#updateDisplayAndOrientation()更新DisplayInfo

当WMS中状态发生变化后,在DisplayContent#updateDisplayAndOrientation()方法中更新DisplayInfo对象:

// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
    private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
        ......
        final DisplayInfo overrideDisplayInfo = mShouldOverrideDisplayConfiguration
                ? mDisplayInfo : null;
        mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
                overrideDisplayInfo);
        ......
        return mDisplayInfo;
    }

关于DisplayInfo更新的详细流程,会在WMS模块中进行分析。这里通过DMS#setDisplayInfoOverrideFromWindowManager()方法,将DisplayInfo信息传递给了DMS,DMS中根据新的DisplayInfo更新逻辑屏信息。

2.DMS#setDisplayInfoOverrideFromWindowManagerInternal()更新逻辑屏

进入DMS后,在setDisplayInfoOverrideFromWindowManagerInternal()方法中进行处理:

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

    private void setDisplayInfoOverrideFromWindowManagerInternal(
            int displayId, DisplayInfo info) {
        synchronized (mSyncRoot) {
            // 根据Display Id获取逻辑屏
            LogicalDisplay display = mLogicalDisplays.get(displayId);
            if (display != null) {
                if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
                    // 更新逻辑屏
                    handleLogicalDisplayChanged(displayId, display);
                    // 向WMS发起窗口遍历
                    scheduleTraversalLocked(false);
                }
            }
        }
    }

该方法中:

  • 首先根据displayId得到对应的逻辑屏;
  • 然后调用LogicalDisplay#setDisplayInfoOverrideFromWindowManagerLocked()方法更新DMS中DisplayInfo变量;
  • 如果逻辑屏信息发生了更新,会回调onDisplayChanged()方法,并向WMS发起窗口遍历;

下面来逐一进行分析。

2.1.LogicalDisplay#setDisplayInfoOverrideFromWindowManagerLocked()`更新DMS中DisplayInfo变量

// frameworks/base/services/core/java/com/android/server/display/LogicalDisplay.java

    public boolean setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) {
        if (info != null) {
            if (mOverrideDisplayInfo == null) {
                mOverrideDisplayInfo = new DisplayInfo(info);
                mInfo.set(null);
                return true;
            // 说明mOverrideDisplayInfo和info不同
            } else if (!mOverrideDisplayInfo.equals(info)) {
                // 更新mOverrideDisplayInfo
                mOverrideDisplayInfo.copyFrom(info);
                // 置空mInfo,等待更新
                mInfo.set(null);
                return true;
            }
        } else if (mOverrideDisplayInfo != null) {
            mOverrideDisplayInfo = null;
            mInfo.set(null);
            return true;
        }
        return false;
    }

在该方法中,如果mOverrideDisplayInfo和info不一致,说明逻辑屏发生了变化,则会将info更新到mOverrideDisplayInfo中,并置空mInfo,同时返回true,进行逻辑屏的更新。

mInfo是一个DisplayInfoProxy对象,它内部持有一个DisplayInfo对象,这个对象就是当前系统的全局逻辑屏信息,对mInfo进行更新时,首先需要将它置空。

mOverrideDisplayInfo表示来自WMS中的DisplayInfo对象,也即实际的DisplayInfo对象,在更新mInfo对象时,将使用该对象进行更新。

2.2.sendDisplayEventLocked()发起DisplayListener#onDisplayChanged()回调

如果DisplayInfo发生变化,接下来执行handleLogicalDisplayChanged()方法,该方法中调用sendDisplayEventLocked()方法,最终发起了DisplayManager.DisplayListener#onDisplayChanged()方法的回调:

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
    
    private void sendDisplayEventLocked(int displayId, int event) {
        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
        mHandler.sendMessage(msg);
    }

2.3.scheduleTraversalLocked()向WMS请求发起窗口遍历

在方法中,将向WMS发起遍历窗口请求:

    private void scheduleTraversalLocked(boolean inTraversal) {
        if (!mPendingTraversal && mWindowManagerInternal != null) {
            // 发起标记
            mPendingTraversal = true;
            if (!inTraversal) {
                mHandler.sendEmptyMessage(MSG_REQUEST_TRAVERSAL);
            }
        }
    }

在Handler中,直接调用WMS.requestTraversalFromDisplayManager()向WMS中发起更新请求,在android.display线程进行:

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

    private final class DisplayManagerHandler extends Handler {
        public DisplayManagerHandler(Looper looper) {
            super(looper, null, true /*async*/);
        }
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                ......
                case MSG_REQUEST_TRAVERSAL:
                    // 向WMS发起遍历请求
                    mWindowManagerInternal.requestTraversalFromDisplayManager();
                    break;
                ......
            }
        }
    }

2.4.DMS#getDisplayInfo()完成DisplayInfo更新并返回给其他组件

当mInfo被置空后,将等待被再次更新,而再次更新可以由应用发起,也可以由system_server发起,DMS提供了getDisplayInfo()方法,可以让应用端通过Binder调用触发更新,比如在方向旋转时,应用所在Activity在执行onConfiguration()方法之前,会先进行DisplayInfo的更新:

// frameworks/base/core/java/android/app/ActivityThread.java

    private void updateDisplayInfoLocked() {
        DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
        ......
    }

DMS#getDisplayInfoLocked()方法如下:

// frameworks/base/services/core/java/com/android/server/display/LogicalDisplay.java

    public DisplayInfo getDisplayInfoLocked() {
        if (mInfo.get() == null) {
            DisplayInfo info = new DisplayInfo();
            info.copyFrom(mBaseDisplayInfo);
            if (mOverrideDisplayInfo != null) {
                info.appWidth = mOverrideDisplayInfo.appWidth;
                info.appHeight = mOverrideDisplayInfo.appHeight;
                info.smallestNominalAppWidth = mOverrideDisplayInfo.smallestNominalAppWidth;
                info.smallestNominalAppHeight = mOverrideDisplayInfo.smallestNominalAppHeight;
                info.largestNominalAppWidth = mOverrideDisplayInfo.largestNominalAppWidth;
                info.largestNominalAppHeight = mOverrideDisplayInfo.largestNominalAppHeight;
                info.logicalWidth = mOverrideDisplayInfo.logicalWidth;
                info.logicalHeight = mOverrideDisplayInfo.logicalHeight;
                info.rotation = mOverrideDisplayInfo.rotation;
                info.displayCutout = mOverrideDisplayInfo.displayCutout;
                info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
                info.physicalXDpi = mOverrideDisplayInfo.physicalXDpi;
                info.physicalYDpi = mOverrideDisplayInfo.physicalYDpi;
            }
            mInfo.set(info);
        }
        return mInfo.get();
    }

在该方法中,如果mInfo对象不为null,则直接返回,否则从mOverrideDisplayInfo对象中更新mInfo对象,完成逻辑屏信息的更新。

此时,DMS中第一阶段的流程执行完毕。在这一阶段,主要干了三件事:

  • 1.更新mOverrideDisplayInfo和mInfo对象;
  • 2.回调DisplayListener#onDisplayChanged()方法;
  • 3.向WMS发起遍历窗口请求进行窗口摆放工作;

接下来看下在WMS对WindowState的遍历过程中,和DMS做了哪些交互。

在WMS遍历WindowState进行窗口安置的过程中,和DMS有两处交互:

  • 一是在遍历过程中,会获取窗口所携带控制显示方式的首选参数,并由DMS#setDisplayProperties()方法设置给DMS;
  • 二是在完成所有窗口Surface的安置后,通过DisplayManagerInternal#performTraversal()通知DMS,j进行物理屏相关的配置更新,如界面显示的Layer Stack、区域大小......

下面逐一来看。

3.DMS#setDisplayProperties()设置窗口首选参数

WMS中安置WindowState的Surface的详细流程,会在WMS模块中进行分析。这里只看和这部分内容相关的流程。在DisplayContent中的函数接口对象mApplySurfaceChangesTransaction中,会遍历所有的窗口,并将当前显示窗口中携带的首选参数保持到mTmpApplySurfaceChangesTransactionState对象中:

// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

    private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
        final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
        final boolean obscuredChanged = w.mObscured !=
                mTmpApplySurfaceChangesTransactionState.obscured;
        final RootWindowContainer root = mWmService.mRoot;

        if (!mTmpApplySurfaceChangesTransactionState.obscured) {
            final boolean isDisplayed = w.isDisplayedLw();

            if (isDisplayed && w.isObscuringDisplay()) {
                root.mObscuringWindow = w;
                mTmpApplySurfaceChangesTransactionState.obscured = true;
            }
            // 逻辑屏上是否有内容显示,用于控制多屏镜像
            final boolean displayHasContent = root.handleNotObscuredLocked(w,
                    mTmpApplySurfaceChangesTransactionState.obscured,
                    mTmpApplySurfaceChangesTransactionState.syswin);

            if (!mTmpApplySurfaceChangesTransactionState.displayHasContent
                    && !getDisplayPolicy().isWindowExcludedFromContent(w)) {
                mTmpApplySurfaceChangesTransactionState.displayHasContent |= displayHasContent;
            }

            if (w.mHasSurface && isDisplayed) {
                final int type = w.mAttrs.type;
                // 设置首选帧率
                if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
                        && w.mAttrs.preferredRefreshRate != 0) {
                    mTmpApplySurfaceChangesTransactionState.preferredRefreshRate
                            = w.mAttrs.preferredRefreshRate;
                }
                // 设置preferMinimalPostProcessing
                mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing
                        |= w.mAttrs.preferMinimalPostProcessing;

                final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
                        .getPreferredModeId(w);
                // 设置preferredModeId
                if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
                        && preferredModeId != 0) {
                    mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId;
                }
            }
        }
        ......
    };

遍历完成后,将值保存在mTmpApplySurfaceChangesTransactionState中,并在最后向DMS发出请求,设置显示参数,如刷新率、宽高、是否镜像......

// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
 
    void applySurfaceChangesTransaction() {
         
        mTmpApplySurfaceChangesTransactionState.reset();
        ......
        mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
                mLastHasContent,
                mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
                mTmpApplySurfaceChangesTransactionState.preferredModeId,
                mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
                true /* inTraversal, must call performTraversalInTrans... below */);
    }

这里几个参数含义如下:

  • hasContent表示是否逻辑屏上是否有显示内容,用于控制屏幕的镜像;
  • preferredRefreshRate表示最顶层窗口携带的首选刷新率;
  • preferredModeId表示最顶层窗口携带的首选模式;
  • preferMinimalPostProcessing表示最小的Post-Processing, 这是一个显示专业术语。

4.DMS#performTraversal()更新屏幕配置

当所有窗口安置完毕后,WMS中通过DMS#performTraversal()方法和mDisplayTransaction对象,进行DMS中的事务提交:

// frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
 
    private void applySurfaceChangesTransaction() {
       
        ......
        //遍历每个DisplayContent,对每个Display中的Surface进行Placement操作
        final int count = mChildren.size();
        for (int j = 0; j < count; ++j) {
            final DisplayContent dc = mChildren.get(j);
            dc.applySurfaceChangesTransaction();
        }
        // 通知DMS进行Display侧的属性调整
        mWmService.mDisplayManagerInternal.performTraversal(mDisplayTransaction);
        // 将mDisplayTransaction合并到sGlobalTransaction上
        SurfaceControl.mergeToGlobalTransaction(mDisplayTransaction);
    }

进入DMS后:

//  frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

void performTraversalInternal(SurfaceControl.Transaction t) {
    synchronized (mSyncRoot) {
        // 只有mPendingTraversal才会进行更新
        if (!mPendingTraversal) {
            return;
        }
        // 重新置为false
        mPendingTraversal = false;
        performTraversalLocked(t);
    }
 
    for (DisplayTransactionListener listener : mDisplayTransactionListeners) {
        listener.onDisplayTransaction(t);
    }
}

可以看到,只有mPendingTraversal才会进行更新,而mPendingTraversal恰好在scheduleTraversalLocked()方法中设置为true,接着执行performTraversalLocked()方法,这个方法中将逐步完成DMS的更新:

//  frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

private void performTraversalLocked(SurfaceControl.Transaction t) {
    // 清除DisplayViewport列表
    clearViewportsLocked();
 
    // 遍历所有physical display
    final int count = mDisplayDevices.size();
    for (int i = 0; i < count; i++) {
        DisplayDevice device = mDisplayDevices.get(i);
        // 进行物理屏配置更新
        configureDisplayLocked(t, device);
        device.performTraversalLocked(t);
    }
 
    // Viewport发生变化时,通知IMS
    if (mInputManagerInternal != null) {
        mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT);
    }
}

该方法中,会对每一个physical display进行配置更新,最后如果DisplayViewport发生变化,通知IMS。

4.1.configureDisplayLocked()

该方法如下:

//  frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

private void configureDisplayLocked(SurfaceControl.Transaction t, DisplayDevice device) {
    // 获取物理屏对应的DisplayDeviceInfo对象
    final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
    // 是否允许将该display内容镜像到其他Display上,true表示不允许
    final boolean ownContent = (info.flags & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
    // 获取该physical display对应的logical display对象
    LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
    // 如果该物理屏允许镜像,根据display.hasContentLocked()确定镜像源
    if (!ownContent) {
        if (display != null && !display.hasContentLocked()) {
            display = mLogicalDisplays.get(device.getDisplayIdToMirrorLocked());
        }
        if (display == null) {
            display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
        }
    }
    // 配置logical display
    display.configureDisplayLocked(t, device, info.state == Display.STATE_OFF);
    // 更新viewport type
    final Optional<Integer> viewportType = getViewportType(info);

    // 填充viewport
    populateViewportLocked(viewportType, display.getDisplayIdLocked(), device, info.uniqueId);
}

这个方法中,将完成对逻辑屏、物理屏以及viewport的重新配置。下面逐步进行分析。

4.2.configureDisplayLocked()更新物理屏配置

该方法如下:

// frameworks/base/services/core/java/com/android/server/display/LogicalDisplay.java

    public void configureDisplayLocked(SurfaceControl.Transaction t,
            DisplayDevice device,
            boolean isBlanked) {
        // 设置layer stack id
        device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack);

        // 设置色彩模式
        ......

        // 获取逻辑屏对应DisplayInfo对象
        final DisplayInfo displayInfo = getDisplayInfoLocked();
		// 获取物理屏对应DisplayDeviceInfo对象
        final DisplayDeviceInfo displayDeviceInfo = device.getDisplayDeviceInfoLocked();

        // 设置mTempLayerStackRect区域,这个Rect表示在物理屏上要显示的逻辑屏区域
        mTempLayerStackRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);

        // 确定方向
        int orientation = Surface.ROTATION_0;
        if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
            orientation = displayInfo.rotation;
        }
        orientation = (orientation + displayDeviceInfo.rotation) % 4;

        // 根据显示方向,确定
        boolean rotated = (orientation == Surface.ROTATION_90
                || orientation == Surface.ROTATION_270);
		// 获取physical display 宽高
        int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width;
        int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height;
		// 获取被隐藏区域
        Rect maskingInsets = getMaskingInsets(displayDeviceInfo);
        InsetUtils.rotateInsets(maskingInsets, orientation);
		// physical 尺寸减去隐藏区域的宽高
        physWidth -= maskingInsets.left + maskingInsets.right;
        physHeight -= maskingInsets.top + maskingInsets.bottom;

        int displayRectWidth, displayRectHeight;
        if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
            displayRectWidth = displayInfo.logicalWidth;
            displayRectHeight = displayInfo.logicalHeight;
        } else if (physWidth * displayInfo.logicalHeight
                < physHeight * displayInfo.logicalWidth) {
            // Letter box.信封模式
            displayRectWidth = physWidth;
            displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
        } else {
            // Pillar box. 邮桶模式(两黑边)
            displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
            displayRectHeight = physHeight;
        }
        int displayRectTop = (physHeight - displayRectHeight) / 2;
        int displayRectLeft = (physWidth - displayRectWidth) / 2;
        // 设置mTempDisplayRect区域
        mTempDisplayRect.set(displayRectLeft, displayRectTop,
                displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight);

        // 设置偏移量
        mTempDisplayRect.offset(maskingInsets.left, maskingInsets.top);
		// 应用偏移量mDisplayOffsetX和mDisplayOffsetY
        if (orientation == Surface.ROTATION_0) {
            mTempDisplayRect.offset(mDisplayOffsetX, mDisplayOffsetY);
        } else if (orientation == Surface.ROTATION_90) {
            mTempDisplayRect.offset(mDisplayOffsetY, -mDisplayOffsetX);
        } else if (orientation == Surface.ROTATION_180) {
            mTempDisplayRect.offset(-mDisplayOffsetX, -mDisplayOffsetY);
        } else {  // Surface.ROTATION_270
            mTempDisplayRect.offset(-mDisplayOffsetY, mDisplayOffsetX);
        }
		// 设置physical display投影
        device.setProjectionLocked(t, orientation, mTempLayerStackRect, mTempDisplayRect);
    }

这个方法中,主要做了以下三件事:

  • 首先,设置Layer Stack Id;
  • 然后,确定mTempLayerStackRect和mTempDisplayRect两区域;
  • 最后,向SurfaceFlinger中设置Display投影;

4.3.DisplayDevice#setLayerStackLocked()设置Layer Stack

Layer Stack Id表示每个物理屏在SurfaceFlinger中对应的layer stack,在该Display上显示的layer,都处于该Layer Stack中。在创建逻辑屏时,会指定一个layer stack id, 当灭屏时,Layer Stack Id将被设置为-1, 不会显示任何内容:

// frameworks/base/services/core/java/com/android/server/display/DisplayDevice.java

    public final void setLayerStackLocked(SurfaceControl.Transaction t, int layerStack) {
        if (mCurrentLayerStack != layerStack) {
            // 表示当前layer stack id
            mCurrentLayerStack = layerStack;
            // 设置
            t.setDisplayLayerStack(mDisplayToken, layerStack);
        }
    }

4.4.DisplayDevice#setProjectionLocked()设置投影矩阵

接下来,会确定mTempLayerStackRect和mTempDisplayRect这两对象,mTempLayerStackRect表示逻辑屏大小区域,mTempDisplayRect表示物理屏上显示区域,通过SurfaceControl.Transaction#setDisplayLayerStack()将这两区域传给SurfaceFlinger后,计算得出最终的投影矩阵:

// 
public final void setProjectionLocked(SurfaceControl.Transaction t, int orientation,
        Rect layerStackRect, Rect displayRect) {
    if (mCurrentOrientation != orientation
            || mCurrentLayerStackRect == null
            || !mCurrentLayerStackRect.equals(layerStackRect)
            || mCurrentDisplayRect == null
            || !mCurrentDisplayRect.equals(displayRect)) {
        // 当前屏幕方向
        mCurrentOrientation = orientation;
        // 更新逻辑显示区域和对应的物理显示区域
        ......
        mCurrentLayerStackRect.set(layerStackRect);
        ......
        mCurrentDisplayRect.set(displayRect);
        // 传递给SurfaceControl中计算投影矩阵
        t.setDisplayProjection(mDisplayToken,
                orientation, layerStackRect, displayRect);
    }
}

在这个方法中,会将mTempLayerStackRect和mTempDisplayRect更新给全局变量mCurrentLayerStackRect和mCurrentDisplayRect,将在映射ViewPort时发挥作用。

至此,LogicalDisplay#configureDisplayLocked()方法执行完毕,回到DMS#configureDisplayLocked()方法中,接下来将开始进行viewport的填充。

4.5.更新ViewPort

ViewPort表示物理屏和逻辑屏之间的对应关系,Input模块中,用ViewPort来将物理屏的touch事件坐标转换为逻辑屏的坐标。所以,每次进行逻辑屏和物理屏配置时,都会重新对view port进行填充,并发送给IMS模块。

每次进行performTraversalLocked()时,首先会清空当前ViewPort列表:

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
    
    private void clearViewportsLocked() {
        mViewports.clear();
    }

在执行完LogicalDisplay#configureDisplayLocked()方法后,物理屏的mCurrentOrientation、mCurrentDisplayRect都完成了更新,接下来通过DMS#getViewportType()方法,根据物理屏属性确定对应物理屏新的ViewPort类型:

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

    private Optional<Integer> getViewportType(DisplayDeviceInfo info) {
        // 内置屏,Viewport类型为VIEWPORT_INTERNAL
        if ((info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
            return Optional.of(VIEWPORT_INTERNAL);
        // 设置了TOUCH_EXTERNAL标记,表示touch事件来自外部,Viewport类型为VIEWPORT_INTERNAL
        } else if (info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) {
            return Optional.of(VIEWPORT_EXTERNAL);
        } else if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL
                && !TextUtils.isEmpty(info.uniqueId)) {
            return Optional.of(VIEWPORT_VIRTUAL);
        } else {
        }
        return Optional.empty();
    }

4.6.populateViewportLocked()填充ViewPort

接下来通过DMS#populateViewportLocked()方法,对ViewPort进行填充:

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
    
    private void populateViewportLocked(int viewportType, int displayId, DisplayDevice device,
            DisplayDeviceInfo info) {
        final DisplayViewport viewport = getViewportLocked(viewportType, info.uniqueId);
        // 进入对应物理屏中进行填充
        device.populateViewportLocked(viewport);
        // 表示有效
        viewport.valid = true;
        viewport.displayId = displayId;
        // 根据Display state确定viewport是否处于active状态
        viewport.isActive = Display.isActiveState(info.state);
    }

进入到对应的物理屏中进行填充:

// frameworks/base/services/core/java/com/android/server/display/DisplayDevice.java

    public final void populateViewportLocked(DisplayViewport viewport) {
        // 设置方向
        viewport.orientation = mCurrentOrientation;
        // 设置逻辑屏区域
        if (mCurrentLayerStackRect != null) {
            viewport.logicalFrame.set(mCurrentLayerStackRect);
        } else {
            viewport.logicalFrame.setEmpty();
        }
        // 设置物理屏区域
        if (mCurrentDisplayRect != null) {
            viewport.physicalFrame.set(mCurrentDisplayRect);
        } else {
            viewport.physicalFrame.setEmpty();
        }
        // 是否发生旋转
        boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90
                || mCurrentOrientation == Surface.ROTATION_270);
        // 获取物理屏信息
        DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
        // 表示物理屏宽高
        viewport.deviceWidth = isRotated ? info.height : info.width;
        viewport.deviceHeight = isRotated ? info.width : info.height;
        // 物理屏id
        viewport.uniqueId = info.uniqueId;
        // 物理屏固定的物理端口
        if (info.address instanceof DisplayAddress.Physical) {
            viewport.physicalPort = ((DisplayAddress.Physical) info.address).getPort();
        } else {
            viewport.physicalPort = null;
        }
    }

可以看到,对ViewPort的填充,就是将逻辑屏的显示区域宽高、物理屏的显示区域宽高、以及物理屏的DisplayDeviceInfo对象中携带的信息传递给ViewPort对象。(viewport.physicalPort对象非常重要,在后面会看到还有一种静态路由方式)。

4.7.通知IMS更新ViewPort

DMS#performTraversalLocked()方法的最后,通过Handler发送了一个MSG_UPDATE_VIEWPORT,会将变化后的viewport通知给IMS:

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
    
    private final class DisplayManagerHandler extends Handler {
        ......
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                ......
                case MSG_UPDATE_VIEWPORT: {
                    final boolean changed;
                    synchronized (mSyncRoot) {
                        changed = !mTempViewports.equals(mViewports);
                        if (changed) {
                            mTempViewports.clear();
                            for (DisplayViewport d : mViewports) {
                                mTempViewports.add(d.makeCopy());
                            }
                        }
                    }
                    if (changed) {
                        mInputManagerInternal.setDisplayViewports(mTempViewports);
                    }
                    break;
                }
                ......
            }
        }
    }

到这里,整个DMS中的更新完成,最终随着WMS中事务的提交,完成更新后的显示。

主要过程中主要流程时序图如下:

dms-traversal.jpg