平台
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很像…