平台
RK3288 + Android 7.1 + EDP x 2 (1080P)
问题描述
打开客户的双屏异显应用, 发现副屏的显示布局挤压错乱, 经过测试排查, 发现是副屏的DPI过高导致(320).
解决
- frameworks/base/services/core/java/com/android/server/display/DisplayDeviceInfo.java
public void setAssumedDensityForExternalDisplay(int width, int height) {
//修改DPI XHDPI(320) -> MEDIUM(160), 也可以设置为任意想要的值.
densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_MEDIUM / 1080;
// Technically, these values should be smaller than the apparent density
// but we don't know the physical size of the display.
xDpi = densityDpi;
yDpi = densityDpi;
}
旋转副屏补丁
主要问题可以通过dumpsys display和 dumpsys window中看出, 强制旋转后, 副屏显示的方向存在两个问题
- 主屏已旋转, 但副屏仍然保持横屏
- 虽然已旋转, 但副屏显示的尺寸为 1920x1080, 而不是 1080x1920, 从而导致显示不全等问题
补丁见资源.
- 解决双横屏强制旋转为双竖屏(旋转90度)
- 解决旋转90度后, 副屏显示不完整
调试
dump下显示信息:
- adb shell dumpsys display
DISPLAY MANAGER (dumpsys display)
mOnlyCode=false
mSafeMode=false
mPendingTraversal=false
mGlobalDisplayState=ON
mNextNonDefaultDisplayId=2
mDefaultViewport=DisplayViewport{valid=true, displayId=0, orientation=0, logicalFrame=Rect(0, 0 - 1920, 1080), physicalFrame=Rect(0, 0 - 1920, 1080), deviceWidth=1920, deviceHeight=1080}
mExternalTouchViewport=DisplayViewport{valid=true, displayId=0, orientation=0, logicalFrame=Rect(0, 0 - 1920, 1080), physicalFrame=Rect(0, 0 - 1920, 1080), deviceWidth=1920, deviceHeight=1080}
mDefaultDisplayDefaultColorMode=0
mSingleDisplayDemoMode=false
mWifiDisplayScanRequestCount=0
Display Adapters: size=4
LocalDisplayAdapter
OverlayDisplayAdapter
mCurrentOverlaySetting=
mOverlays: size=0
WifiDisplayAdapter
mCurrentStatus=WifiDisplayStatus{featureState=2, scanState=0, activeDisplayState=0, activeDisplay=null, displays=[], sessionInfo=WifiDisplaySessionInfo:
Client/Owner: Client
GroupId:
Passphrase:
SessionId: 0
IP Address: }
mFeatureState=2
mScanState=0
mActiveDisplayState=0
mActiveDisplay=null
mDisplays=[]
mAvailableDisplays=[]
mRememberedDisplays=[]
mPendingStatusChangeBroadcast=false
mSupportsProtectedBuffers=false
mDisplayController:
mWifiDisplayOnSetting=false
mWifiP2pEnabled=true
mWfdEnabled=false
mWfdEnabling=false
mNetworkInfo=[type: WIFI_P2P[], state: UNKNOWN/IDLE, reason: (unspecified), extra: (none), failover: false, available: true, roaming: false, metered: false]
mScanRequested=false
mDiscoverPeersInProgress=false
mDesiredDevice=null
mConnectingDisplay=null
mDisconnectingDisplay=null
mCancelingDisplay=null
mConnectedDevice=null
mConnectionRetriesLeft=0
mRemoteDisplay=null
mRemoteDisplayInterface=null
mRemoteDisplayConnected=false
mAdvertisedDisplay=null
mAdvertisedDisplaySurface=null
mAdvertisedDisplayWidth=0
mAdvertisedDisplayHeight=0
mAdvertisedDisplayFlags=0
mAvailableWifiDisplayPeers: size=0
VirtualDisplayAdapter
Display Devices: size=2
DisplayDeviceInfo{"内置屏幕": uniqueId="local:0", 1920 x 1080, modeId 1, defaultModeId 1, supportedModes [{id=1, width=1920, height=1080, fps=65.0}], colorMode 0, supportedColorModes [0], HdrCapabilities android.view.Display$HdrCapabilities@a69d6308, density 160, 378.046 x 160.421 dpi, appVsyncOff 1000000, presDeadline 15384615, touch INTERNAL, rotation 0, type BUILT_IN, state ON, FLAG_DEFAULT_DISPLAY, FLAG_ROTATES_WITH_CONTENT, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
mAdapter=LocalDisplayAdapter
mUniqueId=local:0
mDisplayToken=android.os.BinderProxy@997301b
mCurrentLayerStack=0
mCurrentOrientation=0
mCurrentLayerStackRect=Rect(0, 0 - 1920, 1080)
mCurrentDisplayRect=Rect(0, 0 - 1920, 1080)
mCurrentSurface=null
mBuiltInDisplayId=0
mActivePhysIndex=0
mActiveModeId=1
mActiveColorMode=0
mState=ON
mBrightness=205
mBacklight=com.android.server.lights.LightsService$LightImpl@5e9e5b8
mDisplayInfos=
PhysicalDisplayInfo{1920 x 1080, 65.0 fps, density 1.0, 378.046 x 160.421 dpi, secure true, appVsyncOffset 1000000, bufferDeadline 15384615}
mSupportedModes=
DisplayModeRecord{mMode={id=1, width=1920, height=1080, fps=65.0}}
mSupportedColorModes=[0]
DisplayDeviceInfo{"HDMI 屏幕": uniqueId="local:1", 1920 x 1080, modeId 2, defaultModeId 2, supportedModes [{id=2, width=1920, height=1080, fps=63.000004}], colorMode 0, supportedColorModes [0], HdrCapabilities android.view.Display$HdrCapabilities@a69d6308, density 320, 320.0 x 320.0 dpi, appVsyncOff 1000000, presDeadline 15873015, touch EXTERNAL, rotation 0, type HDMI, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION}
mAdapter=LocalDisplayAdapter
mUniqueId=local:1
mDisplayToken=android.os.BinderProxy@ce50f91
mCurrentLayerStack=0
mCurrentOrientation=0
mCurrentLayerStackRect=Rect(0, 0 - 1920, 1080)
mCurrentDisplayRect=Rect(0, 0 - 1920, 1080)
mCurrentSurface=null
mBuiltInDisplayId=1
mActivePhysIndex=0
mActiveModeId=2
mActiveColorMode=0
mState=ON
mBrightness=-1
mBacklight=null
mDisplayInfos=
PhysicalDisplayInfo{1920 x 1080, 63.000004 fps, density 1.33125, 320.0 x 320.0 dpi, secure true, appVsyncOffset 1000000, bufferDeadline 15873015}
mSupportedModes=
DisplayModeRecord{mMode={id=2, width=1920, height=1080, fps=63.000004}}
mSupportedColorModes=[0]
Logical Displays: size=2
Display 0:
mDisplayId=0
mLayerStack=0
mHasContent=true
mRequestedMode=0
mRequestedColorMode=0
mDisplayOffset=(0, 0)
mPrimaryDisplayDevice=内置屏幕
mBaseDisplayInfo=DisplayInfo{"内置屏幕", uniqueId "local:0", app 1920 x 1080, real 1920 x 1080, largest app 1920 x 1080, smallest app 1920 x 1080, mode 1, defaultMode 1, modes [{id=1, width=1920, height=1080, fps=65.0}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@a69d6308, rotation 0, density 160 (378.046 x 160.421) dpi, layerStack 0, appVsyncOff 1000000, presDeadline 15384615, type BUILT_IN, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
mOverrideDisplayInfo=DisplayInfo{"内置屏幕", uniqueId "local:0", app 1920 x 1024, real 1920 x 1080, largest app 1920 x 1840, smallest app 1080 x 1000, mode 1, defaultMode 1, modes [{id=1, width=1920, height=1080, fps=65.0}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@a69d6308, rotation 0, density 160 (378.046 x 160.421) dpi, layerStack 0, appVsyncOff 1000000, presDeadline 15384615, type BUILT_IN, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
Display 1:
mDisplayId=1
mLayerStack=1
mHasContent=false
mRequestedMode=0
mRequestedColorMode=0
mDisplayOffset=(0, 0)
mPrimaryDisplayDevice=HDMI 屏幕
mBaseDisplayInfo=DisplayInfo{"HDMI 屏幕", uniqueId "local:1", app 1920 x 1080, real 1920 x 1080, largest app 1920 x 1080, smallest app 1920 x 1080, mode 2, defaultMode 2, modes [{id=2, width=1920, height=1080, fps=63.000004}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@a69d6308, rotation 0, density 320 (320.0 x 320.0) dpi, layerStack 1, appVsyncOff 1000000, presDeadline 15873015, type HDMI, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION}
mOverrideDisplayInfo=DisplayInfo{"HDMI 屏幕", uniqueId "local:1", app 1920 x 1080, real 1920 x 1080, largest app 1920 x 1080, smallest app 1920 x 1080, mode 2, defaultMode 2, modes [{id=2, width=1920, height=1080, fps=63.000004}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@a69d6308, rotation 0, density 320 (320.0 x 320.0) dpi, layerStack 1, appVsyncOff 1000000, presDeadline 15873015, type HDMI, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION}
从上面的信息可以看得, 副屏HDMI 屏幕使用的是320的DPI.
分析排查
从dump出来的信息着手, 查找相关的变量PhysicalDisplayInfo信息来源:
- frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java
private void tryConnectDisplayLocked(int builtInDisplayId) {
IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
if (displayToken != null) {
SurfaceControl.PhysicalDisplayInfo[] configs =
SurfaceControl.getDisplayConfigs(displayToken);
}
- frameworks/base/core/java/android/view/SurfaceControl.java
public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
}
return nativeGetDisplayConfigs(displayToken);
}
private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
IBinder displayToken);
- frameworks/base/core/jni/android_view_SurfaceControl.cpp
static jobjectArray nativeGetDisplayConfigs(JNIEnv* env, jclass clazz,
jobject tokenObj) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
if (token == NULL) return NULL;
Vector<DisplayInfo> configs;
if (SurfaceComposerClient::getDisplayConfigs(token, &configs) != NO_ERROR ||
configs.size() == 0) {
return NULL;
}
//...
}
- frameworks/native/libs/gui/SurfaceComposerClient.cpp
status_t SurfaceComposerClient::getDisplayConfigs(
const sp<IBinder>& display, Vector<DisplayInfo>* configs)
{
return ComposerService::getComposerService()->getDisplayConfigs(display, configs);
}
void ComposerService::connectLocked() {
const String16 name("SurfaceFlinger");
while (getService(name, &mComposerService) != NO_ERROR) {
usleep(250000);
}
assert(mComposerService != NULL);
// Create the death listener.
class DeathObserver : public IBinder::DeathRecipient {
ComposerService& mComposerService;
virtual void binderDied(const wp<IBinder>& who) {
ALOGW("ComposerService remote (surfaceflinger) died [%p]",
who.unsafe_get());
mComposerService.composerServiceDied();
}
public:
DeathObserver(ComposerService& mgr) : mComposerService(mgr) { }
};
mDeathObserver = new DeathObserver(*const_cast<ComposerService*>(this));
IInterface::asBinder(mComposerService)->linkToDeath(mDeathObserver);
}
几经跳转, 获取的是SurfaceFlinger服务.
- frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
status_t SurfaceFlinger::getDisplayConfigs(const sp<IBinder>& display,
Vector<DisplayInfo>* configs) {
if ((configs == NULL) || (display.get() == NULL)) {
return BAD_VALUE;
}
if (!display.get())
return NAME_NOT_FOUND;
int32_t type = NAME_NOT_FOUND;
for (int i=0 ; i<DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES ; i++) {
if (display == mBuiltinDisplays[i]) {
type = i;
break;
}
}
if (type < 0) {
return type;
}
// TODO: Not sure if display density should handled by SF any longer
class Density {
static int getDensityFromProperty(char const* propName) {
char property[PROPERTY_VALUE_MAX];
int density = 0;
if (property_get(propName, property, NULL) > 0) {
density = atoi(property);
}
return density;
}
public:
static int getEmuDensity() {
return getDensityFromProperty("qemu.sf.lcd_density"); }
static int getBuildDensity() {
return getDensityFromProperty("ro.sf.lcd_density"); }
};
configs->clear();
for (const auto& hwConfig : getHwComposer().getConfigs(type)) {
DisplayInfo info = DisplayInfo();
float xdpi = hwConfig->getDpiX();
float ydpi = hwConfig->getDpiY();
if (type == DisplayDevice::DISPLAY_PRIMARY) {
// The density of the device is provided by a build property
float density = Density::getBuildDensity() / 160.0f;
if (density == 0) {
// the build doesn't provide a density -- this is wrong!
// use xdpi instead
ALOGE("ro.sf.lcd_density must be defined as a build property");
density = xdpi / 160.0f;
}
if (Density::getEmuDensity()) {
// if "qemu.sf.lcd_density" is specified, it overrides everything
xdpi = ydpi = density = Density::getEmuDensity();
density /= 160.0f;
}
info.density = density;
// TODO: this needs to go away (currently needed only by webkit)
sp<const DisplayDevice> hw(getDefaultDisplayDevice());
info.orientation = hw->getOrientation();
} else {
// TODO: where should this value come from?
static const int TV_DENSITY = 213;
info.density = TV_DENSITY / 160.0f;
info.orientation = 0;
}
info.w = hwConfig->getWidth();
info.h = hwConfig->getHeight();
info.xdpi = xdpi;
info.ydpi = ydpi;
info.fps = 1e9 / hwConfig->getVsyncPeriod();
info.appVsyncOffset = VSYNC_EVENT_PHASE_OFFSET_NS;
// This is how far in advance a buffer must be queued for
// presentation at a given time. If you want a buffer to appear
// on the screen at time N, you must submit the buffer before
// (N - presentationDeadline).
//
// Normally it's one full refresh period (to give SF a chance to
// latch the buffer), but this can be reduced by configuring a
// DispSync offset. Any additional delays introduced by the hardware
// composer or panel must be accounted for here.
//
// We add an additional 1ms to allow for processing time and
// differences between the ideal and actual refresh rate.
info.presentationDeadline = hwConfig->getVsyncPeriod() -
SF_VSYNC_EVENT_PHASE_OFFSET_NS + 1000000;
// All non-virtual displays are currently considered secure.
info.secure = true;
configs->push_back(info);
}
return NO_ERROR;
}
- frameworks/native/services/surfaceflinger/SurfaceFlinger.h
HWComposer& getHwComposer() const { return *mHwc; }
- frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
mHwc = new HWComposer(this);
}
- frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.cpp
HWComposer::HWComposer(const sp<SurfaceFlinger>& flinger)
: mFlinger(flinger),
mAdapter(),
mHwcDevice(),
mDisplayData(2),
mFreeDisplaySlots(),
mHwcDisplaySlots(),
mCBContext(),
mEventHandler(nullptr),
mVSyncCounts(),
mRemainingHwcVirtualDisplays(0)
{
for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) {
mLastHwVSync[i] = 0;
mVSyncCounts[i] = 0;
}
loadHwcModule();
}
// Load and prepare the hardware composer module. Sets mHwc.
void HWComposer::loadHwcModule()
{
ALOGV("loadHwcModule");
hw_module_t const* module;
if (hw_get_module(HWC_HARDWARE_MODULE_ID, &module) != 0) {
ALOGE("%s module not found, aborting", HWC_HARDWARE_MODULE_ID);
abort();
}
hw_device_t* device = nullptr;
int error = module->methods->open(module, HWC_HARDWARE_COMPOSER, &device);
if (error != 0) {
ALOGE("Failed to open HWC device (%s), aborting", strerror(-error));
abort();
}
uint32_t majorVersion = (device->version >> 24) & 0xF;
if (majorVersion == 2) {
mHwcDevice = std::make_unique<HWC2::Device>(
reinterpret_cast<hwc2_device_t*>(device));
} else {
mAdapter = std::make_unique<HWC2On1Adapter>(
reinterpret_cast<hwc_composer_device_1_t*>(device));
uint8_t minorVersion = mAdapter->getHwc1MinorVersion();
if (minorVersion < 1) {
ALOGE("Cannot adapt to HWC version %d.%d",
static_cast<int32_t>((minorVersion >> 8) & 0xF),
static_cast<int32_t>(minorVersion & 0xF));
abort();
}
mHwcDevice = std::make_unique<HWC2::Device>(
static_cast<hwc2_device_t*>(mAdapter.get()));
}
mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount();
}
加载HW库(/system/lib/hw/hwcomposer.rk30board.so)
- hardware/rockchip/hwcomposer/hwcomposer.cpp
static float getDefaultDensity(uint32_t width, uint32_t height) {
// Default density is based on TVs: 1080p displays get XHIGH density,
// lower-resolution displays get TV density. Maybe eventually we'll need
// to update it for 4K displays, though hopefully those just report
// accurate DPI information to begin with. This is also used for virtual
// displays and even primary displays with older hwcomposers, so be
// careful about orientation.
uint32_t h = width < height ? width : height;
if (h >= 1080) return ACONFIGURATION_DENSITY_XHIGH;//320
else return ACONFIGURATION_DENSITY_TV;//213
}
static int hwc_get_display_attributes(struct hwc_composer_device_1 *dev,
int display, uint32_t config,
const uint32_t *attributes,
int32_t *values) {
UN_USED(config);
struct hwc_context_t *ctx = (struct hwc_context_t *)&dev->common;
DrmConnector *c = ctx->drm.GetConnectorFromType(display);
if (!c) {
ALOGE("Failed to get DrmConnector for display %d", display);
return -ENODEV;
}
hwc_drm_display_t *hd = &ctx->displays[c->display()];
if (!hd->active)
return -ENODEV;
uint32_t mm_width = c->mm_width();
uint32_t mm_height = c->mm_height();
int w = hd->framebuffer_width;
int h = hd->framebuffer_height;
int vrefresh = hd->vrefresh;
for (int i = 0; attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE; ++i) {
switch (attributes[i]) {
case HWC_DISPLAY_VSYNC_PERIOD:
values[i] = 1000 * 1000 * 1000 / vrefresh;
break;
case HWC_DISPLAY_WIDTH:
values[i] = w;
break;
case HWC_DISPLAY_HEIGHT:
values[i] = h;
break;
case HWC_DISPLAY_DPI_X:
/* Dots per 1000 inches */
values[i] = mm_width ? (w * UM_PER_INCH) / mm_width : getDefaultDensity(w,h)*1000;
break;
case HWC_DISPLAY_DPI_Y:
/* Dots per 1000 inches */
values[i] =
mm_height ? (h * UM_PER_INCH) / mm_height : getDefaultDensity(w,h)*1000;
break;
}
}
return 0;
}
if (h >= 1080) return ACONFIGURATION_DENSITY_XHIGH;//320这部分代码, 直接返回了320的DPI
尝试修改为ACONFIGURATION_DENSITY_MEDIUM, 成功把PhysicalDisplayInfo修改为160 dpi
但是DisplayDeviceInfo中的DPI依然是320, 那么问题并不是出在PhysicalDisplayInfo
回过头, 重新看看dump的函数:
- frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
private void dumpInternal(PrintWriter pw) {
pw.println("DISPLAY MANAGER (dumpsys display)");
//...
pw.println("Display Devices: size=" + mDisplayDevices.size());
for (DisplayDevice device : mDisplayDevices) {
pw.println(" " + device.getDisplayDeviceInfoLocked());
device.dumpLocked(ipw);
}
//....
}
- frameworks/base/services/core/java/com/android/server/display/DisplayDevice.java
/**
* Gets information about the display device.
*
* The information returned should not change between calls unless the display
* adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event and
* {@link #applyPendingDisplayDeviceInfoChangesLocked()} has been called to apply
* the pending changes.
*
* @return The display device info, which should be treated as immutable by the caller.
* The display device should allocate a new display device info object whenever
* the data changes.
*/
public abstract DisplayDeviceInfo getDisplayDeviceInfoLocked();
- frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java
private final class LocalDisplayDevice extends DisplayDevice
@Override
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
if (mInfo == null) {
SurfaceControl.PhysicalDisplayInfo phys = mDisplayInfos[mActivePhysIndex];
mInfo = new DisplayDeviceInfo();
mInfo.width = phys.width;
//...
mInfo.name = getContext().getResources().getString(
com.android.internal.R.string.display_manager_hdmi_display_name);
mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
mInfo.setAssumedDensityForExternalDisplay(phys.width, phys.height);
}
}
- frameworks/base/services/core/java/com/android/server/display/DisplayDeviceInfo.java
public void setAssumedDensityForExternalDisplay(int width, int height) {
densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
// Technically, these values should be smaller than the apparent density
// but we don't know the physical size of the display.
xDpi = densityDpi;
yDpi = densityDpi;
}
找到了setAssumedDensityForExternalDisplay, 这熟悉的味道, 跟HW很像…