自学内容网 自学内容网

Android 镜像模式和扩展模式区别探讨-Android14

同屏显示和异屏显示探讨

DisplayManagerService启动及主屏添加-Android13
Android主副屏显示-Android14


1、区分镜像模式和扩展模式

LogicalDisplay.java#mHasContent    当前LogicalDisplay是否有内容显示
DisplayContent.java#mLastHasContent  当前DisplayContent是否有内容显示


Android14上默认扩展屏没有显示内容mHasContent=false,扩展屏显示的是主屏DEFAULT_DISPLAY镜像

1.1 扩展屏是否有显示内容

ActivityOptions副屏启动 有Activity启动到扩展屏上,就表示扩展屏上有显示内容,即 mHasContent=true

  • LogicalDisplay.java#mHasContent是由DisplayContent.java#mLastHasContent设置下去的,就是mTmpApplySurfaceChangesTransactionState.displayHasContent
  • RootWindowContainer.java界面刷新时,在mApplySurfaceChangesTransaction中同步,forAllWindows(mApplySurfaceChangesTransaction, true) 当扩展屏没有界面mChildren就会返回 false
  • RootWindowContainer.java#handleNotObscuredLocked 有界面时,界面对应 DisplayContent 是主屏isDefaultDisplay就为 displayHasContent = true; ,而扩展屏界面对应的 DisplayContent 判断主屏不是屏保和锁屏(!mObscureApplicationContentOnSecondaryDisplays)、或者 扩展屏是解锁状态(displayContent.isKeyguardAlwaysUnlocked(),默认无Display.FLAG_ALWAYS_UNLOCKED标志,是未解锁状态,该条件为false)、或者 界面覆盖且为TYPE_KEYGUARD_DIALOG类型((obscured && type == TYPE_KEYGUARD_DIALOG)),某个条件满足就为 displayHasContent = true;

frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java#applySurfaceChangesTransaction

mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) {
    mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
            mLastHasContent,
            mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
            mTmpApplySurfaceChangesTransactionState.preferredModeId,
            mTmpApplySurfaceChangesTransactionState.preferredMinRefreshRate,
            mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate,
            mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
            mTmpApplySurfaceChangesTransactionState.disableHdrConversion,
            true /* inTraversal, must call performTraversalInTrans... below */);
}
private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
    final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
    final boolean obscuredChanged = w.mObscured !=
            mTmpApplySurfaceChangesTransactionState.obscured;
    final RootWindowContainer root = mWmService.mRoot;

    // Update effect.
    w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured;

    if (!mTmpApplySurfaceChangesTransactionState.obscured) {
        final boolean isDisplayed = w.isDisplayed();

        if (isDisplayed && w.isObscuringDisplay()) {
            // This window completely covers everything behind it, so we want to leave all
            // of them as undimmed (for performance reasons).
            mObscuringWindow = w;
            mTmpApplySurfaceChangesTransactionState.obscured = true;
        }

        final boolean displayHasContent = root.handleNotObscuredLocked(w,
                mTmpApplySurfaceChangesTransactionState.obscured,
                mTmpApplySurfaceChangesTransactionState.syswin);

        if (!mTmpApplySurfaceChangesTransactionState.displayHasContent
                && !getDisplayPolicy().isWindowExcludedFromContent(w)) {
            mTmpApplySurfaceChangesTransactionState.displayHasContent |= displayHasContent;
        }

        if (w.mHasSurface && isDisplayed) {
            if ((w.mAttrs.flags & FLAG_KEEP_SCREEN_ON) != 0) {
                mTmpHoldScreenWindow = w;
            } else if (w == mLastWakeLockHoldingWindow) {
                ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON,
                        "handleNotObscuredLocked: %s was holding screen wakelock but no longer "
                                + "has FLAG_KEEP_SCREEN_ON!!! called by%s",
                        w, Debug.getCallers(10));
            }

            final int type = w.mAttrs.type;
            if (type == TYPE_SYSTEM_DIALOG
                    || type == TYPE_SYSTEM_ERROR
                    || (type == TYPE_NOTIFICATION_SHADE
                        &&  mWmService.mPolicy.isKeyguardShowing())) {
                mTmpApplySurfaceChangesTransactionState.syswin = true;
            }
            if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
                    && w.mAttrs.preferredRefreshRate != 0) {
                mTmpApplySurfaceChangesTransactionState.preferredRefreshRate
                        = w.mAttrs.preferredRefreshRate;
            }

            mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing
                    |= w.mAttrs.preferMinimalPostProcessing;

            mTmpApplySurfaceChangesTransactionState.disableHdrConversion
                    |= !(w.mAttrs.isHdrConversionEnabled());

            final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
                    .getPreferredModeId(w);

            if (w.getWindowingMode() != WINDOWING_MODE_PINNED
                    && mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
                    && preferredModeId != 0) {
                mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId;
            }

            final float preferredMinRefreshRate = getDisplayPolicy().getRefreshRatePolicy()
                    .getPreferredMinRefreshRate(w);
            if (mTmpApplySurfaceChangesTransactionState.preferredMinRefreshRate == 0
                    && preferredMinRefreshRate != 0) {
                mTmpApplySurfaceChangesTransactionState.preferredMinRefreshRate =
                        preferredMinRefreshRate;
            }

            final float preferredMaxRefreshRate = getDisplayPolicy().getRefreshRatePolicy()
                    .getPreferredMaxRefreshRate(w);
            if (mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate == 0
                    && preferredMaxRefreshRate != 0) {
                mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate =
                        preferredMaxRefreshRate;
            }
        }
    }

    if (obscuredChanged && w.isVisible() && mWallpaperController.isWallpaperTarget(w)) {
        // This is the wallpaper target and its obscured state changed... make sure the
        // current wallpaper's visibility has been updated accordingly.
        mWallpaperController.updateWallpaperVisibility();
    }

    w.handleWindowMovedIfNeeded();

    final WindowStateAnimator winAnimator = w.mWinAnimator;

    //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
    w.resetContentChanged();

    // Moved from updateWindowsAndWallpaperLocked().
    if (w.mHasSurface) {
        // Take care of the window being ready to display.
        final boolean committed = winAnimator.commitFinishDrawingLocked();
        if (isDefaultDisplay && committed) {
            if (w.hasWallpaper()) {
                ProtoLog.v(WM_DEBUG_WALLPAPER,
                        "First draw done in potential wallpaper target %s", w);
                mWallpaperMayChange = true;
                pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                if (DEBUG_LAYOUT_REPEATS) {
                    surfacePlacer.debugLayoutRepeats(
                            "wallpaper and commitFinishDrawingLocked true",
                            pendingLayoutChanges);
                }
            }
        }
    }

    final ActivityRecord activity = w.mActivityRecord;
    if (activity != null && activity.isVisibleRequested()) {
        activity.updateLetterboxSurface(w);
        final boolean updateAllDrawn = activity.updateDrawnWindowStates(w);
        if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(activity)) {
            mTmpUpdateAllDrawn.add(activity);
        }
    }

    w.updateResizingWindowIfNeeded();
};

frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java#handleNotObscuredLocked

boolean handleNotObscuredLocked(WindowState w, boolean obscured, boolean syswin) {
    final WindowManager.LayoutParams attrs = w.mAttrs;
    final int attrFlags = attrs.flags;
    final boolean onScreen = w.isOnScreen();
    final boolean canBeSeen = w.isDisplayed();
    final int privateflags = attrs.privateFlags;
    boolean displayHasContent = false;

    ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON,
            "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w"
                    + ".isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
            w, w.mHasSurface, onScreen, w.isDisplayed(), w.mAttrs.userActivityTimeout);
    if (w.mHasSurface && onScreen) {
        if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) {
            mUserActivityTimeout = w.mAttrs.userActivityTimeout;
            ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "mUserActivityTimeout set to %d",
                    mUserActivityTimeout);
        }
    }
    if (w.mHasSurface && canBeSeen) {
        if (!syswin && w.mAttrs.screenBrightness >= 0
                && Float.isNaN(mScreenBrightnessOverride)) {
            mScreenBrightnessOverride = w.mAttrs.screenBrightness;
        }

        final int type = attrs.type;
        // This function assumes that the contents of the default display are processed first
        // before secondary displays.
        final DisplayContent displayContent = w.getDisplayContent();
        if (displayContent != null && displayContent.isDefaultDisplay) {
            // While a dream or keyguard is showing, obscure ordinary application content on
            // secondary displays (by forcibly enabling mirroring unless there is other content
            // we want to show) but still allow opaque keyguard dialogs to be shown.
            if (w.isDreamWindow() || mWmService.mPolicy.isKeyguardShowing()) {
                mObscureApplicationContentOnSecondaryDisplays = true;
            }
            displayHasContent = true;
        } else if (displayContent != null &&
                (!mObscureApplicationContentOnSecondaryDisplays
                        || displayContent.isKeyguardAlwaysUnlocked()
                        || (obscured && type == TYPE_KEYGUARD_DIALOG))) {
            // Allow full screen keyguard presentation dialogs to be seen, or simply ignore the
            // keyguard if this display is always unlocked.
            displayHasContent = true;
        }
        if ((privateflags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) {
            mSustainedPerformanceModeCurrent = true;
        }
    }

    return displayHasContent;
}

1.2 镜像模式显示条件

Android14上默认扩展屏没有显示内容mHasContent=false,扩展屏显示的是主屏DEFAULT_DISPLAY镜像。其实还有两个条件。

  1. 扩展屏信息 getDisplayDeviceInfoLocked 的flag没有DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY
  2. 扩展屏是否可以显示镜像 displayDevice.isWindowManagerMirroringLocked() ,如果为 false,则SurfaceFlinger不会在此显示器上执行层镜像,该方法目前Android14返回就是false固定值。
  3. mContentRecorder.updateRecording()更新镜像时,如果扩展屏有内容 mDisplayContent.getLastHasContent() 或者扩展屏时关闭状态 mDisplayContent.getDisplayInfo().state == Display.STATE_OFF ,就会停止镜像pauseRecording()

frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

void updateRecording() {
    if (mContentRecorder == null || !mContentRecorder.isContentRecordingSessionSet()) {
        if (!setDisplayMirroring()) {
            return;
        }
    }

    mContentRecorder.updateRecording();
}

boolean setDisplayMirroring() {
    int mirrorDisplayId = mWmService.mDisplayManagerInternal.getDisplayIdToMirror(mDisplayId);
    if (mirrorDisplayId == INVALID_DISPLAY) {
        return false;
    }

    if (mirrorDisplayId == mDisplayId) {
        if (mDisplayId != DEFAULT_DISPLAY) {
            ProtoLog.w(WM_DEBUG_CONTENT_RECORDING,
                    "Content Recording: Attempting to mirror self on %d", mirrorDisplayId);
        }
        return false;
    }

    // This is very unlikely, and probably impossible, but if the current display is
    // DEFAULT_DISPLAY and the displayId to mirror results in an invalid display, we don't want
    // to mirror the DEFAULT_DISPLAY so instead we just return
    DisplayContent mirrorDc = mRootWindowContainer.getDisplayContentOrCreate(mirrorDisplayId);
    if (mirrorDc == null && mDisplayId == DEFAULT_DISPLAY) {
        ProtoLog.w(WM_DEBUG_CONTENT_RECORDING,
                "Content Recording: Found no matching mirror display for id=%d for "
                        + "DEFAULT_DISPLAY. Nothing to mirror.",
                mirrorDisplayId);
        return false;
    }

    if (mirrorDc == null) {
        mirrorDc = mRootWindowContainer.getDefaultDisplay();
        ProtoLog.w(WM_DEBUG_CONTENT_RECORDING,
                "Content Recording: Attempting to mirror %d from %d but no DisplayContent "
                        + "associated. Changing to mirror default display.",
                mirrorDisplayId, mDisplayId);
    }

    // Create a session for mirroring the display content to this virtual display.
    ContentRecordingSession session = ContentRecordingSession
            .createDisplaySession(mirrorDc.getDisplayId())
            .setVirtualDisplayId(mDisplayId);
    setContentRecordingSession(session);
    ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
            "Content Recording: Successfully created a ContentRecordingSession for "
                    + "displayId=%d to mirror content from displayId=%d",
            mDisplayId, mirrorDisplayId);
    return true;
}

frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

public int getDisplayIdToMirror(int displayId) {
    synchronized (mSyncRoot) {
        final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
        if (display == null) {
            return Display.INVALID_DISPLAY;
        }

        final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
        final boolean ownContent = (displayDevice.getDisplayDeviceInfoLocked().flags
                & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
        // If the display has enabled mirroring, but specified that it will be managed by
        // WindowManager, return an invalid display id. This is to ensure we don't
        // accidentally select the display id to mirror based on DM logic and instead allow
        // the caller to specify what area to mirror.
        if (ownContent || displayDevice.isWindowManagerMirroringLocked()) {
            return Display.INVALID_DISPLAY;
        }

        int displayIdToMirror = displayDevice.getDisplayIdToMirrorLocked();
        LogicalDisplay displayToMirror = mLogicalDisplayMapper.getDisplayLocked(
                displayIdToMirror);
        // If the displayId for the requested mirror doesn't exist, fallback to mirroring
        // default display.
        if (displayToMirror == null) {
            displayIdToMirror = Display.DEFAULT_DISPLAY;
        }
        return displayIdToMirror;
    }
}

frameworks/base/services/core/java/com/android/server/wm/ContentRecorder.java

/**
 * Start recording if this DisplayContent no longer has content. Pause recording if it now
 * has content or the display is not on.
 */
@VisibleForTesting void updateRecording() {
    if (isCurrentlyRecording() && (mDisplayContent.getLastHasContent()
            || mDisplayContent.getDisplayInfo().state == Display.STATE_OFF)) {
        pauseRecording();
    } else {
        // Display no longer has content, or now has a surface to write to, so try to start
        // recording.
        startRecordingIfNeeded();
    }
}

2、镜像模式界面

  • 创建镜像SurfaceControlmRecordedSurface = SurfaceControl.mirrorSurface(mRecordedWindowContainer.getSurfaceControl())
  • 创建镜像SurfaceControl对应的TransactionSurfaceControl.Transaction transaction = mDisplayContent.mWmService.mTransactionFactory.get().reparent(mRecordedSurface, mDisplayContent.getSurfaceControl()).reparent(mDisplayContent.getWindowingLayer(), null).reparent(mDisplayContent.getOverlayLayer(), null);
  • 根据主屏和扩展屏大小处理:mRecordedWindowContainer.getBounds(), surfaceSize, updateMirroredSurface

frameworks/base/services/core/java/com/android/server/wm/ContentRecorder.java

/**
 * Start recording to this DisplayContent if it does not have its own content. Captures the
 * content of a WindowContainer indicated by a WindowToken. If unable to start recording, falls
 * back to original MediaProjection approach.
 */
private void startRecordingIfNeeded() {
    // Only record if this display does not have its own content, is not recording already,
    // and if this display is on (it has a surface to write output to).
    if (mDisplayContent.getLastHasContent() || isCurrentlyRecording()
            || mDisplayContent.getDisplayInfo().state == Display.STATE_OFF
            || mContentRecordingSession == null) {
        return;
    }

    if (mContentRecordingSession.isWaitingForConsent()) {
        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do "
                + "nothing");
        return;
    }

    mRecordedWindowContainer = retrieveRecordedWindowContainer();
    if (mRecordedWindowContainer == null) {
        // Either the token is missing, or the window associated with the token is missing.
        // Error has already been handled, so just leave.
        return;
    }

    final Point surfaceSize = fetchSurfaceSizeIfPresent();
    if (surfaceSize == null) {
        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                "Content Recording: Unable to start recording for display %d since the "
                        + "surface is not available.",
                mDisplayContent.getDisplayId());
        return;
    }
    ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
            "Content Recording: Display %d has no content and is on, so start recording for "
                    + "state %d",
            mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state);

    // TODO(b/274790702): Do not start recording if waiting for consent - for now,
    //  go ahead.

    // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
    mRecordedSurface = SurfaceControl.mirrorSurface(
            mRecordedWindowContainer.getSurfaceControl());
    SurfaceControl.Transaction transaction =
            mDisplayContent.mWmService.mTransactionFactory.get()
                    // Set the mMirroredSurface's parent to the root SurfaceControl for this
                    // DisplayContent. This brings the new mirrored hierarchy under this
                    // DisplayContent,
                    // so SurfaceControl will write the layers of this hierarchy to the
                    // output surface
                    // provided by the app.
                    .reparent(mRecordedSurface, mDisplayContent.getSurfaceControl())
                    // Reparent the SurfaceControl of this DisplayContent to null, to prevent
                    // content
                    // being added to it. This ensures that no app launched explicitly on the
                    // VirtualDisplay will show up as part of the mirrored content.
                    .reparent(mDisplayContent.getWindowingLayer(), null)
                    .reparent(mDisplayContent.getOverlayLayer(), null);
    // Retrieve the size of the DisplayArea to mirror.
    updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);

    // Notify the client about the visibility of the mirrored region, now that we have begun
    // capture.
    if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
        mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
                mRecordedWindowContainer.asTask().isVisibleRequested());
    } else {
        int currentDisplayState =
                mRecordedWindowContainer.asDisplayContent().getDisplayInfo().state;
        mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
                currentDisplayState != DISPLAY_STATE_OFF);
    }

    // No need to clean up. In SurfaceFlinger, parents hold references to their children. The
    // mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is
    // holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up
    // when the VirtualDisplay is destroyed - which will clean up this DisplayContent.
}
/**
  * Apply transformations to the mirrored surface to ensure the captured contents are scaled to
  * fit and centred in the output surface.
  *
  * @param transaction           the transaction to include transformations of mMirroredSurface
  *                              to. Transaction is not applied before returning.
  * @param recordedContentBounds bounds of the content to record to the surface provided by
  *                              the app.
  * @param surfaceSize           the default size of the surface to write the display area
  *                              content to
  */
 @VisibleForTesting void updateMirroredSurface(SurfaceControl.Transaction transaction,
         Rect recordedContentBounds, Point surfaceSize) {
     // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
     // output surface.
     float scaleX = surfaceSize.x / (float) recordedContentBounds.width();
     float scaleY = surfaceSize.y / (float) recordedContentBounds.height();
     float scale = Math.min(scaleX, scaleY);
     int scaledWidth = Math.round(scale * (float) recordedContentBounds.width());
     int scaledHeight = Math.round(scale * (float) recordedContentBounds.height());

     // Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored
     // contents in the output surface.
     int shiftedX = 0;
     if (scaledWidth != surfaceSize.x) {
         shiftedX = (surfaceSize.x - scaledWidth) / 2;
     }
     int shiftedY = 0;
     if (scaledHeight != surfaceSize.y) {
         shiftedY = (surfaceSize.y - scaledHeight) / 2;
     }

     transaction
             // Crop the area to capture to exclude the 'extra' wallpaper that is used
             // for parallax (b/189930234).
             .setWindowCrop(mRecordedSurface, recordedContentBounds.width(),
                     recordedContentBounds.height())
             // Scale the root mirror SurfaceControl, based upon the size difference between the
             // source (DisplayArea to capture) and output (surface the app reads images from).
             .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
             // Position needs to be updated when the mirrored DisplayArea has changed, since
             // the content will no longer be centered in the output surface.
             .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */)
             .apply();
     mLastRecordedBounds = new Rect(recordedContentBounds);
     // Request to notify the client about the resize.
     mMediaProjectionManager.notifyActiveProjectionCapturedContentResized(
             mLastRecordedBounds.width(), mLastRecordedBounds.height());
 }

原文地址:https://blog.csdn.net/qq_23452385/article/details/144316396

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!