Loading services/core/java/com/android/server/wm/AppWindowToken.java +104 −0 Original line number Diff line number Diff line Loading @@ -239,6 +239,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>(); ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>(); /** * The scale to fit at least one side of the activity to its parent. If the activity uses * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5. */ private float mSizeCompatScale = 1f; /** * The bounds in global coordinates for activity in size compatibility mode. * @see ActivityRecord#inSizeCompatMode */ private Rect mSizeCompatBounds; private boolean mDisablePreviewScreenshots; private Task mLastParent; Loading Loading @@ -1563,11 +1574,52 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return mOrientation; } /** @return {@code true} if the compatibility bounds is taking effect. */ boolean inSizeCompatMode() { return mSizeCompatBounds != null; } @Override float getSizeCompatScale() { return inSizeCompatMode() ? mSizeCompatScale : super.getSizeCompatScale(); } /** * @return Non-empty bounds if the activity has override bounds. * @see ActivityRecord#resolveOverrideConfiguration(Configuration) */ Rect getResolvedOverrideBounds() { // Get bounds from resolved override configuration because it is computed with orientation. return getResolvedOverrideConfiguration().windowConfiguration.getBounds(); } @Override public void onConfigurationChanged(Configuration newParentConfig) { final int prevWinMode = getWindowingMode(); mTmpPrevBounds.set(getBounds()); super.onConfigurationChanged(newParentConfig); final Task task = getTask(); final Rect overrideBounds = getResolvedOverrideBounds(); if (task != null && !overrideBounds.isEmpty() // If the changes come from change-listener, the incoming parent configuration is // still the old one. Make sure their orientations are the same to reduce computing // the compatibility bounds for the intermediate state. && getResolvedOverrideConfiguration().orientation == newParentConfig.orientation) { final Rect taskBounds = task.getBounds(); // Since we only center the activity horizontally, if only the fixed height is smaller // than its container, the override bounds don't need to take effect. if ((overrideBounds.width() != taskBounds.width() || overrideBounds.height() > taskBounds.height())) { calculateCompatBoundsTransformation(newParentConfig); updateSurfacePosition(); } else if (mSizeCompatBounds != null) { mSizeCompatBounds = null; mSizeCompatScale = 1f; updateSurfacePosition(); } } final int winMode = getWindowingMode(); if (prevWinMode == winMode) { Loading Loading @@ -1681,6 +1733,54 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return mThumbnail; } /** * Calculates the scale and offset to horizontal center the size compatibility bounds into the * region which is available to application. */ private void calculateCompatBoundsTransformation(Configuration newParentConfig) { final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds(); final Rect viewportBounds = parentAppBounds != null ? parentAppBounds : newParentConfig.windowConfiguration.getBounds(); final Rect contentBounds = getResolvedOverrideBounds(); final float contentW = contentBounds.width(); final float contentH = contentBounds.height(); final float viewportW = viewportBounds.width(); final float viewportH = viewportBounds.height(); // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1 : Math.min(viewportW / contentW, viewportH / contentH); final int offsetX = (int) ((viewportW - contentW * mSizeCompatScale + 1) * 0.5f) + viewportBounds.left; if (mSizeCompatBounds == null) { mSizeCompatBounds = new Rect(); } mSizeCompatBounds.set(contentBounds); mSizeCompatBounds.offsetTo(0, 0); mSizeCompatBounds.scale(mSizeCompatScale); mSizeCompatBounds.left += offsetX; mSizeCompatBounds.right += offsetX; } @Override public Rect getBounds() { if (mSizeCompatBounds != null) { return mSizeCompatBounds; } return super.getBounds(); } @Override public boolean matchParentBounds() { if (super.matchParentBounds()) { return true; } // An activity in size compatibility mode may have override bounds which equals to its // parent bounds, so the exact bounds should also be checked. final WindowContainer parent = getParent(); return parent == null || parent.getBounds().equals(getResolvedOverrideBounds()); } @Override void checkAppWindowsReadyToShow() { if (allDrawn == mLastAllDrawn) { Loading Loading @@ -2875,6 +2975,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (mPendingRelaunchCount != 0) { pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount); } if (mSizeCompatScale != 1f || mSizeCompatBounds != null) { pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds=" + mSizeCompatBounds); } if (mRemovingFromDisplay) { pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay); } Loading services/core/java/com/android/server/wm/WindowManagerService.java +3 −3 Original line number Diff line number Diff line Loading @@ -47,7 +47,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; Loading Loading @@ -1839,6 +1838,9 @@ public class WindowManagerService extends IWindowManager.Stub return; } outDisplayFrame.set(win.getDisplayFrameLw()); if (win.inSizeCompatMode()) { outDisplayFrame.scale(win.mInvGlobalScale); } } } Loading Loading @@ -1963,8 +1965,6 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility + " req=" + requestedWidth + "x" + requestedHeight + " " + win.mAttrs); winAnimator.mSurfaceDestroyDeferred = (flags & RELAYOUT_DEFER_SURFACE_DESTROY) != 0; win.mEnforceSizeCompat = (win.mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0; if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) { winAnimator.mAlpha = attrs.alpha; } Loading services/core/java/com/android/server/wm/WindowState.java +74 −23 Original line number Diff line number Diff line Loading @@ -246,7 +246,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean mIsWallpaper; private final boolean mIsFloatingLayer; int mSeq; boolean mEnforceSizeCompat; int mViewVisibility; int mSystemUiVisibility; /** Loading Loading @@ -505,7 +504,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private PowerManager.WakeLock mDrawLock; final private Rect mTmpRect = new Rect(); private final Rect mTmpRect = new Rect(); private final Point mTmpPoint = new Point(); /** * Whether the window was resized by us while it was gone for layout. Loading Loading @@ -655,7 +655,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mContext = mWmService.mContext; DeathRecipient deathRecipient = new DeathRecipient(); mSeq = seq; mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0; mPowerManagerWrapper = powerManagerWrapper; mForceSeamlesslyRotate = token.mRoundedCornerOverlay; if (localLOGV) Slog.v( Loading Loading @@ -732,6 +731,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSession.windowAddedLocked(mAttrs.packageName); } /** * @return {@code true} if the application runs in size compatibility mode. * @see android.content.res.CompatibilityInfo#supportsScreen * @see ActivityRecord#inSizeCompatMode */ boolean inSizeCompatMode() { return (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0 || (mAppToken != null && mAppToken.inSizeCompatMode() // Exclude starting window because it is not displayed by the application. && mAttrs.type != TYPE_APPLICATION_STARTING); } /** * Returns whether this {@link WindowState} has been considered for drawing by its parent. */ Loading Loading @@ -995,7 +1006,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWindowFrames.offsetFrames(-layoutXDiff, -layoutYDiff); mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame); if (mEnforceSizeCompat) { if (inSizeCompatMode()) { // If there is a size compatibility scale being applied to the // window, we need to apply this to its insets so that they are // reported to the app in its coordinate space. Loading Loading @@ -1354,8 +1365,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void prelayout() { if (mEnforceSizeCompat) { mGlobalScale = getDisplayContent().mCompatibleScreenScale; if (inSizeCompatMode()) { mGlobalScale = mToken.getSizeCompatScale(); mInvGlobalScale = 1 / mGlobalScale; } else { mGlobalScale = mInvGlobalScale = 1; Loading Loading @@ -2145,6 +2156,30 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP int getSurfaceTouchableRegion(Region region, int flags) { final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; if (mAppToken != null && !mAppToken.getResolvedOverrideBounds().isEmpty()) { // There may have touchable letterboxes around the activity, so in order to let the // letterboxes are able to receive touch event and slip to activity, the activity with // compatibility bounds cannot occupy full screen touchable region. if (modal) { // A modal window uses the whole compatibility bounds. flags |= FLAG_NOT_TOUCH_MODAL; mTmpRect.set(mAppToken.getResolvedOverrideBounds()); // TODO(b/112288258): Remove the forced offset when the override bounds always // starts from zero (See {@link ActivityRecord#resolveOverrideConfiguration}). mTmpRect.offsetTo(0, 0); } else { // Non-modal uses the application based frame. mTmpRect.set(mWindowFrames.mCompatFrame); } // The offset of compatibility bounds is applied to surface of {@link #AppWindowToken} // and frame, so it is unnecessary to translate twice in surface based coordinates. final int surfaceOffsetX = mAppToken.inSizeCompatMode() ? mAppToken.getBounds().left : 0; mTmpRect.offset(surfaceOffsetX - mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top); region.set(mTmpRect); return flags; } if (modal && mAppToken != null) { // Limit the outer touch to the activity stack region. flags |= FLAG_NOT_TOUCH_MODAL; Loading Loading @@ -2943,7 +2978,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING) Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING"); final Rect frame = mWindowFrames.mFrame; final Rect frame = mWindowFrames.mCompatFrame; final Rect overscanInsets = mWindowFrames.mLastOverscanInsets; final Rect contentInsets = mWindowFrames.mLastContentInsets; final Rect visibleInsets = mWindowFrames.mLastVisibleInsets; Loading Loading @@ -3353,7 +3388,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(prefix + "mHasSurface=" + mHasSurface + " isReadyForDisplay()=" + isReadyForDisplay() + " mWindowRemovalAllowed=" + mWindowRemovalAllowed); if (mEnforceSizeCompat) { if (inSizeCompatMode()) { pw.println(prefix + "mCompatFrame=" + mWindowFrames.mCompatFrame.toShortString(sTmpSB)); } if (dumpAll) { Loading Loading @@ -3477,17 +3512,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float x, y; int w,h; final boolean inSizeCompatMode = inSizeCompatMode(); if ((mAttrs.flags & FLAG_SCALED) != 0) { if (mAttrs.width < 0) { w = pw; } else if (mEnforceSizeCompat) { } else if (inSizeCompatMode) { w = (int)(mAttrs.width * mGlobalScale + .5f); } else { w = mAttrs.width; } if (mAttrs.height < 0) { h = ph; } else if (mEnforceSizeCompat) { } else if (inSizeCompatMode) { h = (int)(mAttrs.height * mGlobalScale + .5f); } else { h = mAttrs.height; Loading @@ -3495,21 +3531,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { if (mAttrs.width == MATCH_PARENT) { w = pw; } else if (mEnforceSizeCompat) { } else if (inSizeCompatMode) { w = (int)(mRequestedWidth * mGlobalScale + .5f); } else { w = mRequestedWidth; } if (mAttrs.height == MATCH_PARENT) { h = ph; } else if (mEnforceSizeCompat) { } else if (inSizeCompatMode) { h = (int)(mRequestedHeight * mGlobalScale + .5f); } else { h = mRequestedHeight; } } if (mEnforceSizeCompat) { if (inSizeCompatMode) { x = mAttrs.x * mGlobalScale; y = mAttrs.y * mGlobalScale; } else { Loading Loading @@ -3537,7 +3573,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // We need to make sure we update the CompatFrame as it is used for // cropping decisions, etc, on systems where we lack a decor layer. mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame); if (mEnforceSizeCompat) { if (inSizeCompatMode) { // See comparable block in computeFrameLw. mWindowFrames.mCompatFrame.scale(mInvGlobalScale); } Loading Loading @@ -3650,7 +3686,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float translateToWindowX(float x) { float winX = x - mWindowFrames.mFrame.left; if (mEnforceSizeCompat) { if (inSizeCompatMode()) { winX *= mGlobalScale; } return winX; Loading @@ -3658,7 +3694,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float translateToWindowY(float y) { float winY = y - mWindowFrames.mFrame.top; if (mEnforceSizeCompat) { if (inSizeCompatMode()) { winY *= mGlobalScale; } return winY; Loading Loading @@ -4233,12 +4269,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ void calculatePolicyCrop(Rect policyCrop) { final DisplayContent displayContent = getDisplayContent(); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); if (!isDefaultDisplay()) { if (!displayContent.isDefaultDisplay && !displayContent.supportsSystemDecorations()) { // On a different display there is no system decor. Crop the window // by the screen boundaries. // TODO(multi-display) final DisplayInfo displayInfo = displayContent.getDisplayInfo(); policyCrop.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height()); policyCrop.intersect(-mWindowFrames.mCompatFrame.left, -mWindowFrames.mCompatFrame.top, Loading Loading @@ -4304,7 +4339,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // scale function because we want to round things to make the crop // always round to a larger rect to ensure we don't crop too // much and hide part of the window that should be seen. if (mEnforceSizeCompat && mInvGlobalScale != 1.0f) { if (inSizeCompatMode() && mInvGlobalScale != 1.0f) { final float scale = mInvGlobalScale; systemDecorRect.left = (int) (systemDecorRect.left * scale - 0.5f); systemDecorRect.top = (int) (systemDecorRect.top * scale - 0.5f); Loading Loading @@ -4664,8 +4699,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Since the parent was outset by its surface insets, we need to undo the outsetting // with insetting by the same amount. final WindowState parent = getParentWindow(); outPoint.offset(-parent.mWindowFrames.mFrame.left + parent.mAttrs.surfaceInsets.left, -parent.mWindowFrames.mFrame.top + parent.mAttrs.surfaceInsets.top); transformSurfaceInsetsPosition(mTmpPoint, parent.mAttrs.surfaceInsets); outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x, -parent.mWindowFrames.mFrame.top + mTmpPoint.y); } else if (parentWindowContainer != null) { final Rect parentBounds = parentWindowContainer.getDisplayedBounds(); outPoint.offset(-parentBounds.left, -parentBounds.top); Loading @@ -4683,7 +4719,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } // Expand for surface insets. See WindowState.expandForSurfaceInsets. outPoint.offset(-mAttrs.surfaceInsets.left, -mAttrs.surfaceInsets.top); transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets); outPoint.offset(-mTmpPoint.x, -mTmpPoint.y); } /** * The surface insets from layout parameter are in application coordinate. If the window is * scaled, the insets also need to be scaled for surface position in global coordinate. */ private void transformSurfaceInsetsPosition(Point outPos, Rect surfaceInsets) { if (!inSizeCompatMode()) { outPos.x = surfaceInsets.left; outPos.y = surfaceInsets.top; return; } outPos.x = (int) (surfaceInsets.left * mGlobalScale + 0.5f); outPos.y = (int) (surfaceInsets.top * mGlobalScale + 0.5f); } boolean needsRelativeLayeringToIme() { Loading services/core/java/com/android/server/wm/WindowToken.java +8 −0 Original line number Diff line number Diff line Loading @@ -181,6 +181,14 @@ class WindowToken extends WindowContainer<WindowState> { } } /** * @return The scale for applications running in compatibility mode. Multiply the size in the * application by this scale will be the size in the screen. */ float getSizeCompatScale() { return mDisplayContent.mCompatibleScreenScale; } /** * Returns true if the new window is considered greater than the existing window in terms of * z-order. Loading services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +43 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; Loading Loading @@ -213,6 +214,48 @@ public class AppWindowTokenTests extends WindowTestsBase { mWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); } @Test public void testSizeCompatBounds() { // The real surface transaction is unnecessary. mToken.setSkipPrepareSurfaces(true); final Rect fixedBounds = mToken.getRequestedOverrideConfiguration().windowConfiguration .getBounds(); fixedBounds.set(0, 0, 1200, 1600); final Configuration newParentConfig = mTask.getConfiguration(); // Change the size of the container to two times smaller with insets. newParentConfig.windowConfiguration.setAppBounds(200, 0, 800, 800); final Rect containerAppBounds = newParentConfig.windowConfiguration.getAppBounds(); final Rect containerBounds = newParentConfig.windowConfiguration.getBounds(); containerBounds.set(0, 0, 600, 800); mToken.onConfigurationChanged(newParentConfig); assertTrue(mToken.inSizeCompatMode()); assertEquals(containerAppBounds, mToken.getBounds()); assertEquals((float) containerAppBounds.width() / fixedBounds.width(), mToken.getSizeCompatScale(), 0.0001f /* delta */); // Change the width of the container to two times bigger. containerAppBounds.set(0, 0, 2400, 1600); containerBounds.set(containerAppBounds); mToken.onConfigurationChanged(newParentConfig); assertTrue(mToken.inSizeCompatMode()); // Don't scale up, so the bounds keep the same as the fixed width. assertEquals(fixedBounds.width(), mToken.getBounds().width()); // Assert the position is horizontal center. assertEquals((containerAppBounds.width() - fixedBounds.width()) / 2, mToken.getBounds().left); assertEquals(1f, mToken.getSizeCompatScale(), 0.0001f /* delta */); // Change the width of the container to fit the fixed bounds. containerBounds.set(0, 0, 1200, 2000); mToken.onConfigurationChanged(newParentConfig); // Assert don't use fixed bounds because the region is enough. assertFalse(mToken.inSizeCompatMode()); } @Test @Presubmit public void testGetOrientation() { Loading Loading
services/core/java/com/android/server/wm/AppWindowToken.java +104 −0 Original line number Diff line number Diff line Loading @@ -239,6 +239,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>(); ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>(); /** * The scale to fit at least one side of the activity to its parent. If the activity uses * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5. */ private float mSizeCompatScale = 1f; /** * The bounds in global coordinates for activity in size compatibility mode. * @see ActivityRecord#inSizeCompatMode */ private Rect mSizeCompatBounds; private boolean mDisablePreviewScreenshots; private Task mLastParent; Loading Loading @@ -1563,11 +1574,52 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return mOrientation; } /** @return {@code true} if the compatibility bounds is taking effect. */ boolean inSizeCompatMode() { return mSizeCompatBounds != null; } @Override float getSizeCompatScale() { return inSizeCompatMode() ? mSizeCompatScale : super.getSizeCompatScale(); } /** * @return Non-empty bounds if the activity has override bounds. * @see ActivityRecord#resolveOverrideConfiguration(Configuration) */ Rect getResolvedOverrideBounds() { // Get bounds from resolved override configuration because it is computed with orientation. return getResolvedOverrideConfiguration().windowConfiguration.getBounds(); } @Override public void onConfigurationChanged(Configuration newParentConfig) { final int prevWinMode = getWindowingMode(); mTmpPrevBounds.set(getBounds()); super.onConfigurationChanged(newParentConfig); final Task task = getTask(); final Rect overrideBounds = getResolvedOverrideBounds(); if (task != null && !overrideBounds.isEmpty() // If the changes come from change-listener, the incoming parent configuration is // still the old one. Make sure their orientations are the same to reduce computing // the compatibility bounds for the intermediate state. && getResolvedOverrideConfiguration().orientation == newParentConfig.orientation) { final Rect taskBounds = task.getBounds(); // Since we only center the activity horizontally, if only the fixed height is smaller // than its container, the override bounds don't need to take effect. if ((overrideBounds.width() != taskBounds.width() || overrideBounds.height() > taskBounds.height())) { calculateCompatBoundsTransformation(newParentConfig); updateSurfacePosition(); } else if (mSizeCompatBounds != null) { mSizeCompatBounds = null; mSizeCompatScale = 1f; updateSurfacePosition(); } } final int winMode = getWindowingMode(); if (prevWinMode == winMode) { Loading Loading @@ -1681,6 +1733,54 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return mThumbnail; } /** * Calculates the scale and offset to horizontal center the size compatibility bounds into the * region which is available to application. */ private void calculateCompatBoundsTransformation(Configuration newParentConfig) { final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds(); final Rect viewportBounds = parentAppBounds != null ? parentAppBounds : newParentConfig.windowConfiguration.getBounds(); final Rect contentBounds = getResolvedOverrideBounds(); final float contentW = contentBounds.width(); final float contentH = contentBounds.height(); final float viewportW = viewportBounds.width(); final float viewportH = viewportBounds.height(); // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1 : Math.min(viewportW / contentW, viewportH / contentH); final int offsetX = (int) ((viewportW - contentW * mSizeCompatScale + 1) * 0.5f) + viewportBounds.left; if (mSizeCompatBounds == null) { mSizeCompatBounds = new Rect(); } mSizeCompatBounds.set(contentBounds); mSizeCompatBounds.offsetTo(0, 0); mSizeCompatBounds.scale(mSizeCompatScale); mSizeCompatBounds.left += offsetX; mSizeCompatBounds.right += offsetX; } @Override public Rect getBounds() { if (mSizeCompatBounds != null) { return mSizeCompatBounds; } return super.getBounds(); } @Override public boolean matchParentBounds() { if (super.matchParentBounds()) { return true; } // An activity in size compatibility mode may have override bounds which equals to its // parent bounds, so the exact bounds should also be checked. final WindowContainer parent = getParent(); return parent == null || parent.getBounds().equals(getResolvedOverrideBounds()); } @Override void checkAppWindowsReadyToShow() { if (allDrawn == mLastAllDrawn) { Loading Loading @@ -2875,6 +2975,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (mPendingRelaunchCount != 0) { pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount); } if (mSizeCompatScale != 1f || mSizeCompatBounds != null) { pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds=" + mSizeCompatBounds); } if (mRemovingFromDisplay) { pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay); } Loading
services/core/java/com/android/server/wm/WindowManagerService.java +3 −3 Original line number Diff line number Diff line Loading @@ -47,7 +47,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; Loading Loading @@ -1839,6 +1838,9 @@ public class WindowManagerService extends IWindowManager.Stub return; } outDisplayFrame.set(win.getDisplayFrameLw()); if (win.inSizeCompatMode()) { outDisplayFrame.scale(win.mInvGlobalScale); } } } Loading Loading @@ -1963,8 +1965,6 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility + " req=" + requestedWidth + "x" + requestedHeight + " " + win.mAttrs); winAnimator.mSurfaceDestroyDeferred = (flags & RELAYOUT_DEFER_SURFACE_DESTROY) != 0; win.mEnforceSizeCompat = (win.mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0; if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) { winAnimator.mAlpha = attrs.alpha; } Loading
services/core/java/com/android/server/wm/WindowState.java +74 −23 Original line number Diff line number Diff line Loading @@ -246,7 +246,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean mIsWallpaper; private final boolean mIsFloatingLayer; int mSeq; boolean mEnforceSizeCompat; int mViewVisibility; int mSystemUiVisibility; /** Loading Loading @@ -505,7 +504,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private PowerManager.WakeLock mDrawLock; final private Rect mTmpRect = new Rect(); private final Rect mTmpRect = new Rect(); private final Point mTmpPoint = new Point(); /** * Whether the window was resized by us while it was gone for layout. Loading Loading @@ -655,7 +655,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mContext = mWmService.mContext; DeathRecipient deathRecipient = new DeathRecipient(); mSeq = seq; mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0; mPowerManagerWrapper = powerManagerWrapper; mForceSeamlesslyRotate = token.mRoundedCornerOverlay; if (localLOGV) Slog.v( Loading Loading @@ -732,6 +731,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSession.windowAddedLocked(mAttrs.packageName); } /** * @return {@code true} if the application runs in size compatibility mode. * @see android.content.res.CompatibilityInfo#supportsScreen * @see ActivityRecord#inSizeCompatMode */ boolean inSizeCompatMode() { return (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0 || (mAppToken != null && mAppToken.inSizeCompatMode() // Exclude starting window because it is not displayed by the application. && mAttrs.type != TYPE_APPLICATION_STARTING); } /** * Returns whether this {@link WindowState} has been considered for drawing by its parent. */ Loading Loading @@ -995,7 +1006,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWindowFrames.offsetFrames(-layoutXDiff, -layoutYDiff); mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame); if (mEnforceSizeCompat) { if (inSizeCompatMode()) { // If there is a size compatibility scale being applied to the // window, we need to apply this to its insets so that they are // reported to the app in its coordinate space. Loading Loading @@ -1354,8 +1365,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void prelayout() { if (mEnforceSizeCompat) { mGlobalScale = getDisplayContent().mCompatibleScreenScale; if (inSizeCompatMode()) { mGlobalScale = mToken.getSizeCompatScale(); mInvGlobalScale = 1 / mGlobalScale; } else { mGlobalScale = mInvGlobalScale = 1; Loading Loading @@ -2145,6 +2156,30 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP int getSurfaceTouchableRegion(Region region, int flags) { final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; if (mAppToken != null && !mAppToken.getResolvedOverrideBounds().isEmpty()) { // There may have touchable letterboxes around the activity, so in order to let the // letterboxes are able to receive touch event and slip to activity, the activity with // compatibility bounds cannot occupy full screen touchable region. if (modal) { // A modal window uses the whole compatibility bounds. flags |= FLAG_NOT_TOUCH_MODAL; mTmpRect.set(mAppToken.getResolvedOverrideBounds()); // TODO(b/112288258): Remove the forced offset when the override bounds always // starts from zero (See {@link ActivityRecord#resolveOverrideConfiguration}). mTmpRect.offsetTo(0, 0); } else { // Non-modal uses the application based frame. mTmpRect.set(mWindowFrames.mCompatFrame); } // The offset of compatibility bounds is applied to surface of {@link #AppWindowToken} // and frame, so it is unnecessary to translate twice in surface based coordinates. final int surfaceOffsetX = mAppToken.inSizeCompatMode() ? mAppToken.getBounds().left : 0; mTmpRect.offset(surfaceOffsetX - mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top); region.set(mTmpRect); return flags; } if (modal && mAppToken != null) { // Limit the outer touch to the activity stack region. flags |= FLAG_NOT_TOUCH_MODAL; Loading Loading @@ -2943,7 +2978,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING) Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING"); final Rect frame = mWindowFrames.mFrame; final Rect frame = mWindowFrames.mCompatFrame; final Rect overscanInsets = mWindowFrames.mLastOverscanInsets; final Rect contentInsets = mWindowFrames.mLastContentInsets; final Rect visibleInsets = mWindowFrames.mLastVisibleInsets; Loading Loading @@ -3353,7 +3388,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(prefix + "mHasSurface=" + mHasSurface + " isReadyForDisplay()=" + isReadyForDisplay() + " mWindowRemovalAllowed=" + mWindowRemovalAllowed); if (mEnforceSizeCompat) { if (inSizeCompatMode()) { pw.println(prefix + "mCompatFrame=" + mWindowFrames.mCompatFrame.toShortString(sTmpSB)); } if (dumpAll) { Loading Loading @@ -3477,17 +3512,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float x, y; int w,h; final boolean inSizeCompatMode = inSizeCompatMode(); if ((mAttrs.flags & FLAG_SCALED) != 0) { if (mAttrs.width < 0) { w = pw; } else if (mEnforceSizeCompat) { } else if (inSizeCompatMode) { w = (int)(mAttrs.width * mGlobalScale + .5f); } else { w = mAttrs.width; } if (mAttrs.height < 0) { h = ph; } else if (mEnforceSizeCompat) { } else if (inSizeCompatMode) { h = (int)(mAttrs.height * mGlobalScale + .5f); } else { h = mAttrs.height; Loading @@ -3495,21 +3531,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { if (mAttrs.width == MATCH_PARENT) { w = pw; } else if (mEnforceSizeCompat) { } else if (inSizeCompatMode) { w = (int)(mRequestedWidth * mGlobalScale + .5f); } else { w = mRequestedWidth; } if (mAttrs.height == MATCH_PARENT) { h = ph; } else if (mEnforceSizeCompat) { } else if (inSizeCompatMode) { h = (int)(mRequestedHeight * mGlobalScale + .5f); } else { h = mRequestedHeight; } } if (mEnforceSizeCompat) { if (inSizeCompatMode) { x = mAttrs.x * mGlobalScale; y = mAttrs.y * mGlobalScale; } else { Loading Loading @@ -3537,7 +3573,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // We need to make sure we update the CompatFrame as it is used for // cropping decisions, etc, on systems where we lack a decor layer. mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame); if (mEnforceSizeCompat) { if (inSizeCompatMode) { // See comparable block in computeFrameLw. mWindowFrames.mCompatFrame.scale(mInvGlobalScale); } Loading Loading @@ -3650,7 +3686,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float translateToWindowX(float x) { float winX = x - mWindowFrames.mFrame.left; if (mEnforceSizeCompat) { if (inSizeCompatMode()) { winX *= mGlobalScale; } return winX; Loading @@ -3658,7 +3694,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float translateToWindowY(float y) { float winY = y - mWindowFrames.mFrame.top; if (mEnforceSizeCompat) { if (inSizeCompatMode()) { winY *= mGlobalScale; } return winY; Loading Loading @@ -4233,12 +4269,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ void calculatePolicyCrop(Rect policyCrop) { final DisplayContent displayContent = getDisplayContent(); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); if (!isDefaultDisplay()) { if (!displayContent.isDefaultDisplay && !displayContent.supportsSystemDecorations()) { // On a different display there is no system decor. Crop the window // by the screen boundaries. // TODO(multi-display) final DisplayInfo displayInfo = displayContent.getDisplayInfo(); policyCrop.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height()); policyCrop.intersect(-mWindowFrames.mCompatFrame.left, -mWindowFrames.mCompatFrame.top, Loading Loading @@ -4304,7 +4339,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // scale function because we want to round things to make the crop // always round to a larger rect to ensure we don't crop too // much and hide part of the window that should be seen. if (mEnforceSizeCompat && mInvGlobalScale != 1.0f) { if (inSizeCompatMode() && mInvGlobalScale != 1.0f) { final float scale = mInvGlobalScale; systemDecorRect.left = (int) (systemDecorRect.left * scale - 0.5f); systemDecorRect.top = (int) (systemDecorRect.top * scale - 0.5f); Loading Loading @@ -4664,8 +4699,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Since the parent was outset by its surface insets, we need to undo the outsetting // with insetting by the same amount. final WindowState parent = getParentWindow(); outPoint.offset(-parent.mWindowFrames.mFrame.left + parent.mAttrs.surfaceInsets.left, -parent.mWindowFrames.mFrame.top + parent.mAttrs.surfaceInsets.top); transformSurfaceInsetsPosition(mTmpPoint, parent.mAttrs.surfaceInsets); outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x, -parent.mWindowFrames.mFrame.top + mTmpPoint.y); } else if (parentWindowContainer != null) { final Rect parentBounds = parentWindowContainer.getDisplayedBounds(); outPoint.offset(-parentBounds.left, -parentBounds.top); Loading @@ -4683,7 +4719,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } // Expand for surface insets. See WindowState.expandForSurfaceInsets. outPoint.offset(-mAttrs.surfaceInsets.left, -mAttrs.surfaceInsets.top); transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets); outPoint.offset(-mTmpPoint.x, -mTmpPoint.y); } /** * The surface insets from layout parameter are in application coordinate. If the window is * scaled, the insets also need to be scaled for surface position in global coordinate. */ private void transformSurfaceInsetsPosition(Point outPos, Rect surfaceInsets) { if (!inSizeCompatMode()) { outPos.x = surfaceInsets.left; outPos.y = surfaceInsets.top; return; } outPos.x = (int) (surfaceInsets.left * mGlobalScale + 0.5f); outPos.y = (int) (surfaceInsets.top * mGlobalScale + 0.5f); } boolean needsRelativeLayeringToIme() { Loading
services/core/java/com/android/server/wm/WindowToken.java +8 −0 Original line number Diff line number Diff line Loading @@ -181,6 +181,14 @@ class WindowToken extends WindowContainer<WindowState> { } } /** * @return The scale for applications running in compatibility mode. Multiply the size in the * application by this scale will be the size in the screen. */ float getSizeCompatScale() { return mDisplayContent.mCompatibleScreenScale; } /** * Returns true if the new window is considered greater than the existing window in terms of * z-order. Loading
services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +43 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; Loading Loading @@ -213,6 +214,48 @@ public class AppWindowTokenTests extends WindowTestsBase { mWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); } @Test public void testSizeCompatBounds() { // The real surface transaction is unnecessary. mToken.setSkipPrepareSurfaces(true); final Rect fixedBounds = mToken.getRequestedOverrideConfiguration().windowConfiguration .getBounds(); fixedBounds.set(0, 0, 1200, 1600); final Configuration newParentConfig = mTask.getConfiguration(); // Change the size of the container to two times smaller with insets. newParentConfig.windowConfiguration.setAppBounds(200, 0, 800, 800); final Rect containerAppBounds = newParentConfig.windowConfiguration.getAppBounds(); final Rect containerBounds = newParentConfig.windowConfiguration.getBounds(); containerBounds.set(0, 0, 600, 800); mToken.onConfigurationChanged(newParentConfig); assertTrue(mToken.inSizeCompatMode()); assertEquals(containerAppBounds, mToken.getBounds()); assertEquals((float) containerAppBounds.width() / fixedBounds.width(), mToken.getSizeCompatScale(), 0.0001f /* delta */); // Change the width of the container to two times bigger. containerAppBounds.set(0, 0, 2400, 1600); containerBounds.set(containerAppBounds); mToken.onConfigurationChanged(newParentConfig); assertTrue(mToken.inSizeCompatMode()); // Don't scale up, so the bounds keep the same as the fixed width. assertEquals(fixedBounds.width(), mToken.getBounds().width()); // Assert the position is horizontal center. assertEquals((containerAppBounds.width() - fixedBounds.width()) / 2, mToken.getBounds().left); assertEquals(1f, mToken.getSizeCompatScale(), 0.0001f /* delta */); // Change the width of the container to fit the fixed bounds. containerBounds.set(0, 0, 1200, 2000); mToken.onConfigurationChanged(newParentConfig); // Assert don't use fixed bounds because the region is enough. assertFalse(mToken.inSizeCompatMode()); } @Test @Presubmit public void testGetOrientation() { Loading