Android 镜像模式和扩展模式区别探讨-Android14
Android 镜像模式和扩展模式区别探讨
同屏显示和异屏显示探讨
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
镜像。其实还有两个条件。
- 扩展屏信息
getDisplayDeviceInfoLocked
的flag没有DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY
- 扩展屏是否可以显示镜像
displayDevice.isWindowManagerMirroringLocked()
,如果为false
,则SurfaceFlinger
不会在此显示器上执行层镜像,该方法目前Android14
返回就是false
固定值。- 当
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、镜像模式界面
- 创建镜像
SurfaceControl
:mRecordedSurface = SurfaceControl.mirrorSurface(mRecordedWindowContainer.getSurfaceControl())
- 创建镜像
SurfaceControl
对应的Transaction
:SurfaceControl.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)!