这个问题是当初有个客户需求发现的,这个需求是他们的产品屏幕上下经过对称裁剪,正常的情况是出现了界面异常,只有部分显示。客户希望我们这边能适配一下这种特殊的屏。
本来这个问题是想在驱动层和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的真实含义和影响范围。
我认为改法可能不一定要这么改,本文先做一个记录,后续如仍有此需求,可继续探究。