Android 镜像模式和扩展模式区别探讨-Android14
Android 镜像模式和扩展模式区别探讨
- 1、区分镜像模式和扩展模式
- 1.1 扩展屏是否有显示内容
- 1.2 镜像模式显示条件
- 2、镜像模式界面
同屏显示和异屏显示探讨
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 returnDisplayContent 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());}