Android 7.1 修改副屏显示DPI(补充旋转补丁)

平台

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中看出, 强制旋转后, 副屏显示的方向存在两个问题

  1. 主屏已旋转, 但副屏仍然保持横屏
  2. 虽然已旋转, 但副屏显示的尺寸为 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很像…

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值