这个问题是当初有个客户需求发现的,这个需求是他们的产品屏幕上下经过对称裁剪,正常的情况是出现了界面异常,只有部分显示。客户希望我们这边能适配一下这种特殊的屏。

        本来这个问题是想在驱动层和hwc去做的,但相关模块的同事认为这边改动工作量比较大,想让系统的人从上层实现一下功能。

        因此考虑使用wm size强制将系统的分辨率改成适配显示屏的大小,以达到功能的需求。这种做法比较简单,且经过分析,UI的合成也是与普通场景一致,并无过多的消耗,主要是会有以下1点问题:这种做法只针对Android系统,也就是只能是Android系统下有效,也就是说并不通用,例如recovery和bootloader模式下、Linux系统下都没有兼容。但与客户确认其能接受这个问题,因为他们无需考虑其他场景。

        Android提供了一个默认的size override配置 - ro.config.size_override,其值的配置与settings global display_size_forced的值格式一样,以逗号分隔宽高值。

        WMS在起来后会根据settings的display_size_forced值和ro.config.size_override来设置display的override size:

private boolean applyForcedPropertiesForDefaultDisplay() {
    boolean changed = false;
    final DisplayContent displayContent = getDefaultDisplayContentLocked();
    // Display size.
    String sizeStr = Settings.Global.getString(mContext.getContentResolver(),
            Settings.Global.DISPLAY_SIZE_FORCED);
    if (sizeStr == null || sizeStr.length() == 0) {
        sizeStr = SystemProperties.get(SIZE_OVERRIDE, null);
    }
    if (sizeStr != null && sizeStr.length() > 0) {
        final int pos = sizeStr.indexOf(',');
        if (pos > 0 && sizeStr.lastIndexOf(',') == pos) {
            int width, height;
            try {
                width = Integer.parseInt(sizeStr.substring(0, pos));
                height = Integer.parseInt(sizeStr.substring(pos + 1));
                if (displayContent.mBaseDisplayWidth != width
                        || displayContent.mBaseDisplayHeight != height) {
                    ProtoLog.i(WM_ERROR, "FORCED DISPLAY SIZE: %dx%d", width, height);
                    displayContent.updateBaseDisplayMetrics(width, height,
                            displayContent.mBaseDisplayDensity);
                    changed = true;
                }
            } catch (NumberFormatException ex) {
            }
        }
    }

    // Display density.
    final int density = getForcedDisplayDensityForUserLocked(mCurrentUserId);
    if (density != 0 && density != displayContent.mBaseDisplayDensity) {
        displayContent.mBaseDisplayDensity = density;
        changed = true;
    }

    // Display scaling mode.
    int mode = Settings.Global.getInt(mContext.getContentResolver(),
            Settings.Global.DISPLAY_SCALING_FORCE, 0);
    if (displayContent.mDisplayScalingDisabled != (mode != 0)) {
        ProtoLog.i(WM_ERROR, "FORCED DISPLAY SCALING DISABLED");
        displayContent.mDisplayScalingDisabled = true;
        changed = true;
    }
    return changed;
}

        这就给我们提供了原生的做法。配置了ro.config.size_override的值就可以做到系统默认起来后就能适配这种裁剪的显示屏。

原因

        但后面客户发现启动时开机动画出现了异常,就是开机动画会出现了位置不对且变形的现象。这主要是开机动画和SF的宽高值都是使用原始的显示设备宽高,也就是默认起来是,SF中DisplayDevice.setProjection中的frame和viewport默认使用的是原始数据,当system_server起来后,DisplayManagerService获取到display的override信息后向SF更新display的config,导致前后的数据差异,出现了动画的错位现象。

修改

        因此我对三处进行了修改:

        1.使用属性记录当前的display override size值。使用属性的原因是因为bootanimation和SF都是native程序,没有settings的接口,且其启动时间也早于settings。

        2.修改bootanimation获取显示设备的宽高值。

        3.修改SF DisplayDevice初始化时默认的设备宽高值。

DisplayWindowSettings.setForcedSize

        wm size会调用到DisplayWindowSettings.setForcedSize()方法,来将信息记录到settings中,但为了让bootanimation和SF都能获取到相关的信息,因此还需要多一种方法来记录此信息,属性值是最方便简单的手段。

diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 6d5abe1e2f31..9c2e1b3ac955 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -28,6 +28,7 @@ import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -67,6 +68,7 @@ class DisplayWindowSettings {
             final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height);
             Settings.Global.putString(mService.mContext.getContentResolver(),
                     Settings.Global.DISPLAY_SIZE_FORCED, sizeString);
+            SystemProperties.set("persist.display.size_force", sizeString);
         }

         final DisplayInfo displayInfo = displayContent.getDisplayInfo();

修改bootanimation创建的Surface size

diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index e2d4ff6179df..71e37c0c4708 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -434,10 +434,34 @@ status_t BootAnimation::readyToRun() {
     if (error != NO_ERROR)
         return error;

+    int override_width = 0, override_height = 0;
+    char buf[PROPERTY_VALUE_MAX];
+    char *val;
+
     mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
     mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
     ui::Size resolution = displayMode.resolution;
-    resolution = limitSurfaceSize(resolution.width, resolution.height);
+
+    buf[0] = '\0';
+    property_get("persist.display.size_force", buf, "");
+    if (buf[0] == '\0') {
+        property_get("ro.config.size_override", buf, "");
+    }
+    val = strtok(buf, ",");
+    if (val != NULL) {
+        override_width = atoi(val);
+    }
+    val = strtok(NULL, ",");
+    if (val != NULL) {
+        override_height = atoi(val);
+    }
+
+    if (override_width != 0 && override_height != 0) {
+        resolution.width = override_width;
+        resolution.height = override_height;
+    } else {
+        resolution = limitSurfaceSize(resolution.width, resolution.height);
+    }

        bootanimation会去创建一个Surface,这个Surface是通过获取Display的宽高来创建了一个屏幕大小的Surface用来显示动画,然后修改了显示的分辨了,为了纠正显示的异常,我参照了SF中DisplayDevice.setProjection中layerStackSpace和framebufferSpace(对应AndroidP中的viewport和frame)修改了bootanimation中的Surface大小。

 修改DisplayDevice.setProjection

        DisplayDevice.setProjection在SF刚启动时,由于上层没有设置viewport和frame的值,此时会根据display的默认参数创建边界。

        因此针对override,需要修改原来的逻辑:

diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index ca4b6abc03..8ac0a976d3 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -37,6 +37,7 @@
 #include <log/log.h>
 #include <system/window.h>
 #include <ui/GraphicTypes.h>
+#include <cutils/properties.h>

 #include "DisplayDevice.h"
 #include "Layer.h"
@@ -225,25 +226,90 @@ void DisplayDevice::setDisplaySize(int width, int height) {
     mCompositionDisplay->setDisplaySize(ui::Size(width, height));
 }

+void DisplayDevice::getOverrideFrame(int width, int height,
+        int overrideWidth, int overrideHeight, Rect & frame) {
+    if (overrideWidth == 0 || overrideHeight == 0) {
+        frame.set(Rect(width, height));
+        return;
+    }
+
+    bool sfSwap = ((toRotationInt(mPhysicalOrientation) & toRotationInt(android::ui::ROTATION_90))
+            && isPrimary()) ? true : false;
+    int w, h;
+    int frameWidth, frameHeight;
+    w = width;
+    h = height;
+    // look for LogicalDisplay.java setProjectionLocked
+    if (w * overrideHeight < h * overrideWidth) {
+        frameWidth = w;
+        frameHeight = overrideHeight * w / overrideWidth;
+    } else {
+        frameWidth = overrideWidth * h / overrideHeight;
+        frameHeight = h;
+    }
+    frame.set(Rect((w - frameWidth) / 2, (h - frameHeight) / 2,
+            (w + frameWidth) / 2, (h + frameHeight) / 2));
+    if (sfSwap) {
+        std::swap(frame.left, frame.top);
+        std::swap(frame.right, frame.bottom);
+    }
+}
+
 void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpaceRect,
                                   Rect orientedDisplaySpaceRect) {
     mOrientation = orientation;
+    int override_width = 0, override_height = 0;

+    bool sfSwap = ((toRotationInt(mPhysicalOrientation) & toRotationInt(android::ui::ROTATION_90))
+            && isPrimary()) ? true : false;
     if (isPrimary()) {
         sPrimaryDisplayRotationFlags = ui::Transform::toRotationFlags(orientation);
     }

     if (!orientedDisplaySpaceRect.isValid()) {
-        // The destination frame can be invalid if it has never been set,
-        // in that case we assume the whole display size.
-        orientedDisplaySpaceRect = getCompositionDisplay()->getState().displaySpace.bounds;
+        char buf[PROPERTY_VALUE_MAX];
+        char *val;
+        buf[0] = '\0';
+        if (mIsPrimary) {
+            property_get("persist.display.size_force", buf, "");
+            if (buf[0] == '\0') {
+                property_get("ro.config.size_override", buf, "");
+            }
+        }
+        if (buf[0] == '\0') {
+            // The destination frame can be invalid if it has never been set,
+            // in that case we assume the whole display size.
+            orientedDisplaySpaceRect = getCompositionDisplay()->getState().displaySpace.bounds;
+        } else {
+            val = strtok(buf, ",");
+            if (val != NULL) {
+                override_width = atoi(val);
+            }
+            val = strtok(NULL, ",");
+            if (val != NULL) {
+                override_height = atoi(val);
+            }
+            orientedDisplaySpaceRect = getCompositionDisplay()->getState().displaySpace.bounds;
+            getOverrideFrame(orientedDisplaySpaceRect.right, orientedDisplaySpaceRect.bottom, override_width, override_height, orientedDisplaySpaceRect);
+        }
+        if (sfSwap) {
+            std::swap(orientedDisplaySpaceRect.left, orientedDisplaySpaceRect.top);
+            std::swap(orientedDisplaySpaceRect.right, orientedDisplaySpaceRect.bottom);
+        }
     }

     if (layerStackSpaceRect.isEmpty()) {
         // The layerStackSpaceRect can be invalid if it has never been set, in that case
         // we assume the whole framebuffer size.
         layerStackSpaceRect = getCompositionDisplay()->getState().framebufferSpace.bounds;
-        if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
+        if (override_height != 0 && override_width != 0) {
+            layerStackSpaceRect.right = override_width;
+            layerStackSpaceRect.bottom = override_height;
+            if (sfSwap)
+                std::swap(layerStackSpaceRect.right, layerStackSpaceRect.bottom);
+        }
+        if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270
+                || sfSwap) {
             std::swap(layerStackSpaceRect.right, layerStackSpaceRect.bottom);
         }
     }
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 7e4d92308c..ade780293a 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -224,6 +224,8 @@ private:

     std::atomic<nsecs_t> mLastHwVsync = 0;

+    void getOverrideFrame(int width, int height, int overrideWidth,
+            int overrideHeight, Rect& frame);
     // TODO(b/74619554): Remove special cases for primary display.
     const bool mIsPrimary;

         当DisplayDevice.setProjection中传参layerStackSpaceRect和orientedDisplaySpaceRect为无效值时,先去判断persist.display.size_force/ro.config.size_override这两个属性的值来配置这两个的值。

总结

        这种改法我认为不是非常规范,但能实现某些场景下的需求,后续可以尽量完善,提交给Google。

        wm size是一种debug的手段,正常使用时是不会使用到的,因此Google的工程应该没有想到这种小问题(或者人家可能根本不想理会)。也就只有这种特殊的需求才会发现甚至需要去解决。

        其中这里有2个知识点并没有理清:

        1.DMS更新display config的时机。

        2.SF 中layerStackSpace和displaySpace的真实含义和影响范围。

        我认为改法可能不一定要这么改,本文先做一个记录,后续如仍有此需求,可继续探究。