Merge changes I90451c14,I9fb27a0a into pi-dev
* changes: DO NOT MERGE WM: correctly draw the rounded corner / cutout overlay during rotation WM: Fix seamless rotation
This commit is contained in:
@@ -52,6 +52,7 @@ import android.view.View;
|
||||
import android.view.View.OnLayoutChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
@@ -91,6 +92,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
private int mRotation;
|
||||
private DisplayCutoutView mCutoutTop;
|
||||
private DisplayCutoutView mCutoutBottom;
|
||||
private boolean mPendingRotationChange;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
@@ -124,6 +126,21 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
|
||||
@Override
|
||||
public void onDisplayChanged(int displayId) {
|
||||
if (mRotation != RotationUtils.getExactRotation(mContext)) {
|
||||
// We cannot immediately update the orientation. Otherwise
|
||||
// WindowManager is still deferring layout until it has finished dispatching
|
||||
// the config changes, which may cause divergence between what we draw
|
||||
// (new orientation), and where we are placed on the screen (old orientation).
|
||||
// Instead we wait until either:
|
||||
// - we are trying to redraw. This because WM resized our window and told us to.
|
||||
// - the config change has been dispatched, so WM is no longer deferring layout.
|
||||
mPendingRotationChange = true;
|
||||
mOverlay.getViewTreeObserver().addOnPreDrawListener(
|
||||
new RestartingPreDrawListener(mOverlay));
|
||||
mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
|
||||
new RestartingPreDrawListener(mBottomOverlay));
|
||||
|
||||
}
|
||||
updateOrientation();
|
||||
}
|
||||
};
|
||||
@@ -138,12 +155,12 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
mOverlay = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.rounded_corners, null);
|
||||
mCutoutTop = new DisplayCutoutView(mContext, true,
|
||||
this::updateWindowVisibilities);
|
||||
this::updateWindowVisibilities, this);
|
||||
((ViewGroup)mOverlay).addView(mCutoutTop);
|
||||
mBottomOverlay = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.rounded_corners, null);
|
||||
mCutoutBottom = new DisplayCutoutView(mContext, false,
|
||||
this::updateWindowVisibilities);
|
||||
this::updateWindowVisibilities, this);
|
||||
((ViewGroup)mBottomOverlay).addView(mCutoutBottom);
|
||||
|
||||
mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
@@ -201,6 +218,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
|
||||
@Override
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
mPendingRotationChange = false;
|
||||
updateOrientation();
|
||||
if (shouldDrawCutout() && mOverlay == null) {
|
||||
setupDecorations();
|
||||
@@ -208,6 +226,9 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
}
|
||||
|
||||
protected void updateOrientation() {
|
||||
if (mPendingRotationChange) {
|
||||
return;
|
||||
}
|
||||
int newRotation = RotationUtils.getExactRotation(mContext);
|
||||
if (newRotation != mRotation) {
|
||||
mRotation = newRotation;
|
||||
@@ -423,15 +444,17 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
private final int[] mLocation = new int[2];
|
||||
private final boolean mInitialStart;
|
||||
private final Runnable mVisibilityChangedListener;
|
||||
private final ScreenDecorations mDecorations;
|
||||
private int mColor = Color.BLACK;
|
||||
private boolean mStart;
|
||||
private int mRotation;
|
||||
|
||||
public DisplayCutoutView(Context context, boolean start,
|
||||
Runnable visibilityChangedListener) {
|
||||
Runnable visibilityChangedListener, ScreenDecorations decorations) {
|
||||
super(context);
|
||||
mInitialStart = start;
|
||||
mVisibilityChangedListener = visibilityChangedListener;
|
||||
mDecorations = decorations;
|
||||
setId(R.id.display_cutout);
|
||||
}
|
||||
|
||||
@@ -494,10 +517,10 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
}
|
||||
|
||||
private void update() {
|
||||
mStart = isStart();
|
||||
if (!isAttachedToWindow()) {
|
||||
if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
|
||||
return;
|
||||
}
|
||||
mStart = isStart();
|
||||
requestLayout();
|
||||
getDisplay().getDisplayInfo(mInfo);
|
||||
mBounds.setEmpty();
|
||||
@@ -660,4 +683,28 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|
||||
return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
|
||||
RotationUtils.ROTATION_SEASCAPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pre-draw listener, that cancels the draw and restarts the traversal with the updated
|
||||
* window attributes.
|
||||
*/
|
||||
private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
|
||||
|
||||
private final View mView;
|
||||
|
||||
private RestartingPreDrawListener(View view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
mPendingRotationChange = false;
|
||||
mView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
// This changes the window attributes - we need to restart the traversal for them to
|
||||
// take effect.
|
||||
updateOrientation();
|
||||
mView.invalidate();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import android.view.SurfaceSession;
|
||||
import libcore.io.Streams;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.policy.WindowManagerPolicy;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -63,7 +64,7 @@ final class ColorFade {
|
||||
|
||||
// The layer for the electron beam surface.
|
||||
// This is currently hardcoded to be one layer above the boot animation.
|
||||
private static final int COLOR_FADE_LAYER = 0x40000001;
|
||||
private static final int COLOR_FADE_LAYER = WindowManagerPolicy.COLOR_FADE_LAYER;
|
||||
|
||||
// The number of frames to draw when preparing the animation so that it will
|
||||
// be ready to run smoothly. We use 3 frames because we are triple-buffered.
|
||||
|
||||
@@ -157,6 +157,8 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
|
||||
int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
|
||||
/** Need to recompute animations */
|
||||
int FINISH_LAYOUT_REDO_ANIM = 0x0008;
|
||||
/** Layer for the screen off animation */
|
||||
int COLOR_FADE_LAYER = 0x40000001;
|
||||
|
||||
/**
|
||||
* Register shortcuts for window manager to dispatch.
|
||||
|
||||
@@ -1106,11 +1106,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
|
||||
}
|
||||
}
|
||||
|
||||
forAllWindows(w -> {
|
||||
w.forceSeamlesslyRotateIfAllowed(oldRotation, rotation);
|
||||
}, true /* traverseTopToBottom */);
|
||||
|
||||
if (rotateSeamlessly) {
|
||||
forAllWindows(w -> {
|
||||
w.mWinAnimator.seamlesslyRotateWindow(getPendingTransaction(),
|
||||
oldRotation, rotation);
|
||||
}, true /* traverseTopToBottom */);
|
||||
seamlesslyRotate(getPendingTransaction(), oldRotation, rotation);
|
||||
}
|
||||
|
||||
mService.mDisplayManagerInternal.performTraversal(getPendingTransaction());
|
||||
@@ -3700,6 +3701,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
|
||||
super(name, service);
|
||||
}
|
||||
|
||||
@Override
|
||||
SurfaceControl.Builder makeChildSurface(WindowContainer child) {
|
||||
final SurfaceControl.Builder builder = super.makeChildSurface(child);
|
||||
if (child instanceof WindowToken && ((WindowToken) child).mRoundedCornerOverlay) {
|
||||
// To draw above the ColorFade layer during the screen off transition, the
|
||||
// rounded corner overlays need to be at the root of the surface hierarchy.
|
||||
// TODO: move the ColorLayer into the display overlay layer such that this is not
|
||||
// necessary anymore.
|
||||
builder.setParent(null);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
void assignChildLayers(SurfaceControl.Transaction t) {
|
||||
assignChildLayers(t, null /* imeContainer */);
|
||||
@@ -3716,6 +3730,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
|
||||
wt.assignRelativeLayer(t, mTaskStackContainers.getSplitScreenDividerAnchor(), 1);
|
||||
continue;
|
||||
}
|
||||
if (wt.mRoundedCornerOverlay) {
|
||||
wt.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
|
||||
continue;
|
||||
}
|
||||
wt.assignLayer(t, j);
|
||||
wt.assignChildLayers(t);
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.wm;
|
||||
|
||||
import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.view.DisplayInfo;
|
||||
|
||||
import com.android.server.wm.utils.CoordinateTransforms;
|
||||
|
||||
/**
|
||||
* Helper class for forced seamless rotation.
|
||||
*
|
||||
* Works by transforming the window token back into the old display rotation.
|
||||
*
|
||||
* Uses deferTransactionUntil instead of latching on the buffer size to allow for seamless 180
|
||||
* degree rotations.
|
||||
*/
|
||||
public class ForcedSeamlessRotator {
|
||||
|
||||
private final Matrix mTransform = new Matrix();
|
||||
private final float[] mFloat9 = new float[9];
|
||||
|
||||
public ForcedSeamlessRotator(int oldRotation, int newRotation, DisplayInfo info) {
|
||||
final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
|
||||
final int h = flipped ? info.logicalWidth : info.logicalHeight;
|
||||
final int w = flipped ? info.logicalHeight : info.logicalWidth;
|
||||
|
||||
final Matrix tmp = new Matrix();
|
||||
CoordinateTransforms.transformLogicalToPhysicalCoordinates(oldRotation, w, h, mTransform);
|
||||
CoordinateTransforms.transformPhysicalToLogicalCoordinates(newRotation, w, h, tmp);
|
||||
mTransform.postConcat(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a transform to the window token's surface that undoes the effect of the global
|
||||
* display rotation.
|
||||
*/
|
||||
public void unrotate(WindowToken token) {
|
||||
token.getPendingTransaction().setMatrix(token.getSurfaceControl(), mTransform, mFloat9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the transform to the window token's surface that undoes the effect of the global
|
||||
* display rotation.
|
||||
*
|
||||
* Removing the transform and the result of the WindowState's layout are both tied to the
|
||||
* WindowState's next frame, such that they apply at the same time the client draws the
|
||||
* window in the new orientation.
|
||||
*/
|
||||
public void finish(WindowToken token, WindowState win) {
|
||||
mTransform.reset();
|
||||
token.getPendingTransaction().setMatrix(token.mSurfaceControl, mTransform, mFloat9);
|
||||
token.getPendingTransaction().deferTransactionUntil(token.mSurfaceControl,
|
||||
win.mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
|
||||
win.getFrameNumber());
|
||||
win.getPendingTransaction().deferTransactionUntil(win.mSurfaceControl,
|
||||
win.mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
|
||||
win.getFrameNumber());
|
||||
}
|
||||
}
|
||||
@@ -735,6 +735,20 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seamlessly rotates the container, by recomputing the location in the new
|
||||
* rotation, and rotating buffers until they are updated for the new rotation.
|
||||
*
|
||||
* @param t the transaction to perform the seamless rotation in
|
||||
* @param oldRotation the rotation we are rotating from
|
||||
* @param newRotation the rotation we are rotating to
|
||||
*/
|
||||
void seamlesslyRotate(Transaction t, int oldRotation, int newRotation) {
|
||||
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
||||
mChildren.get(i).seamlesslyRotate(t, oldRotation, newRotation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this container is opaque and fills all the space made available by its parent
|
||||
* container.
|
||||
|
||||
@@ -1883,6 +1883,12 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
}
|
||||
|
||||
win.setFrameNumber(frameNumber);
|
||||
|
||||
if (win.mPendingForcedSeamlessRotate != null && !mWaitingForConfig) {
|
||||
win.mPendingForcedSeamlessRotate.finish(win.mToken, win);
|
||||
win.mPendingForcedSeamlessRotate = null;
|
||||
}
|
||||
|
||||
int attrChanges = 0;
|
||||
int flagChanges = 0;
|
||||
if (attrs != null) {
|
||||
|
||||
@@ -150,6 +150,8 @@ import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
|
||||
import static com.android.server.wm.WindowStateProto.VISIBLE_FRAME;
|
||||
import static com.android.server.wm.WindowStateProto.VISIBLE_INSETS;
|
||||
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
|
||||
import static com.android.server.wm.utils.CoordinateTransforms.transformRect;
|
||||
import static com.android.server.wm.utils.CoordinateTransforms.transformToRotation;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.app.AppOpsManager;
|
||||
@@ -278,6 +280,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
private boolean mDragResizing;
|
||||
private boolean mDragResizingChangeReported = true;
|
||||
private int mResizeMode;
|
||||
/**
|
||||
* Special mode that is intended only for the rounded corner overlay: during rotation
|
||||
* transition, we un-rotate the window token such that the window appears as it did before the
|
||||
* rotation.
|
||||
*/
|
||||
final boolean mForceSeamlesslyRotate;
|
||||
ForcedSeamlessRotator mPendingForcedSeamlessRotate;
|
||||
|
||||
private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
|
||||
|
||||
@@ -667,6 +676,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
|
||||
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
|
||||
|
||||
void forceSeamlesslyRotateIfAllowed(int oldRotation, int rotation) {
|
||||
if (mForceSeamlesslyRotate) {
|
||||
mPendingForcedSeamlessRotate = new ForcedSeamlessRotator(
|
||||
oldRotation, rotation, getDisplayInfo());
|
||||
mPendingForcedSeamlessRotate.unrotate(this.mToken);
|
||||
}
|
||||
}
|
||||
|
||||
interface PowerManagerWrapper {
|
||||
void wakeUp(long time, String reason);
|
||||
|
||||
@@ -713,6 +730,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
mSeq = seq;
|
||||
mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
|
||||
mPowerManagerWrapper = powerManagerWrapper;
|
||||
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
|
||||
if (localLOGV) Slog.v(
|
||||
TAG, "Window " + this + " client=" + c.asBinder()
|
||||
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
|
||||
@@ -1811,7 +1829,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
&& (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
|
||||
&& !isDragResizing() && !adjustedForMinimizedDockOrIme
|
||||
&& getWindowConfiguration().hasMovementAnimations()
|
||||
&& !mWinAnimator.mLastHidden) {
|
||||
&& !mWinAnimator.mLastHidden
|
||||
&& !mSeamlesslyRotated) {
|
||||
startMoveAnimation(left, top);
|
||||
}
|
||||
|
||||
@@ -4697,7 +4716,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
|
||||
transformFrameToSurfacePosition(mFrame.left, mFrame.top, mSurfacePosition);
|
||||
|
||||
if (!mSurfaceAnimator.hasLeash() && !mLastSurfacePosition.equals(mSurfacePosition)) {
|
||||
// Freeze position while we're unrotated, so the surface remains at the position it was
|
||||
// prior to the rotation.
|
||||
if (!mSurfaceAnimator.hasLeash() && mPendingForcedSeamlessRotate == null &&
|
||||
!mLastSurfacePosition.equals(mSurfacePosition)) {
|
||||
t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
|
||||
mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
|
||||
if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) {
|
||||
@@ -4850,6 +4872,31 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|
||||
mFrameNumber = frameNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
void seamlesslyRotate(Transaction t, int oldRotation, int newRotation) {
|
||||
// Invisible windows, the wallpaper, and force seamlessly rotated windows do not participate
|
||||
// in the regular seamless rotation animation.
|
||||
if (!isVisibleNow() || mIsWallpaper || mForceSeamlesslyRotate) {
|
||||
return;
|
||||
}
|
||||
final Matrix transform = mTmpMatrix;
|
||||
|
||||
mService.markForSeamlessRotation(this, true);
|
||||
|
||||
// We rotated the screen, but have not performed a new layout pass yet. In the mean time,
|
||||
// we recompute the coordinates of mFrame in the new orientation, so the surface can be
|
||||
// properly placed.
|
||||
transformToRotation(oldRotation, newRotation, getDisplayInfo(), transform);
|
||||
transformRect(transform, mFrame, null /* tmpRectF */);
|
||||
|
||||
updateSurfacePosition(t);
|
||||
mWinAnimator.seamlesslyRotate(t, oldRotation, newRotation);
|
||||
|
||||
// Dispatch to children only after mFrame has been updated, as it's needed in the
|
||||
// child's updateSurfacePosition.
|
||||
super.seamlesslyRotate(t, oldRotation, newRotation);
|
||||
}
|
||||
|
||||
private final class MoveAnimationSpec implements AnimationSpec {
|
||||
|
||||
private final long mDuration;
|
||||
|
||||
@@ -46,13 +46,13 @@ import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
|
||||
import static com.android.server.wm.WindowStateAnimatorProto.LAST_CLIP_RECT;
|
||||
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
|
||||
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
|
||||
import static com.android.server.wm.utils.CoordinateTransforms.transformToRotation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.os.Debug;
|
||||
import android.os.Trace;
|
||||
@@ -685,8 +685,11 @@ class WindowStateAnimator {
|
||||
final int displayId = mWin.getDisplayId();
|
||||
final ScreenRotationAnimation screenRotationAnimation =
|
||||
mAnimator.getScreenRotationAnimationLocked(displayId);
|
||||
final boolean screenAnimation =
|
||||
screenRotationAnimation != null && screenRotationAnimation.isAnimating();
|
||||
final boolean windowParticipatesInScreenRotationAnimation =
|
||||
!mWin.mForceSeamlesslyRotate;
|
||||
final boolean screenAnimation = screenRotationAnimation != null
|
||||
&& screenRotationAnimation.isAnimating()
|
||||
&& windowParticipatesInScreenRotationAnimation;
|
||||
|
||||
if (screenAnimation) {
|
||||
// cache often used attributes locally
|
||||
@@ -798,6 +801,13 @@ class WindowStateAnimator {
|
||||
return false;
|
||||
}
|
||||
|
||||
// During forced seamless rotation, the surface bounds get updated with the crop in the
|
||||
// new rotation, which is not compatible with showing the surface in the old rotation.
|
||||
// To work around that we disable cropping for such windows, as it is not necessary anyways.
|
||||
if (w.mForceSeamlesslyRotate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're animating, the wallpaper should only
|
||||
// be updated at the end of the animation.
|
||||
if (w.mAttrs.type == TYPE_WALLPAPER) {
|
||||
@@ -1492,40 +1502,14 @@ class WindowStateAnimator {
|
||||
}
|
||||
}
|
||||
|
||||
void seamlesslyRotateWindow(SurfaceControl.Transaction t,
|
||||
int oldRotation, int newRotation) {
|
||||
void seamlesslyRotate(SurfaceControl.Transaction t, int oldRotation, int newRotation) {
|
||||
final WindowState w = mWin;
|
||||
if (!w.isVisibleNow() || w.mIsWallpaper) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Rect cropRect = mService.mTmpRect;
|
||||
final Rect displayRect = mService.mTmpRect2;
|
||||
final RectF frameRect = mService.mTmpRectF;
|
||||
// We rotated the screen, but have not received a new buffer with the correct size yet. In
|
||||
// the mean time, we rotate the buffer we have to the new orientation.
|
||||
final Matrix transform = mService.mTmpTransform;
|
||||
|
||||
final float x = w.mFrame.left;
|
||||
final float y = w.mFrame.top;
|
||||
final float width = w.mFrame.width();
|
||||
final float height = w.mFrame.height();
|
||||
|
||||
mService.getDefaultDisplayContentLocked().getBounds(displayRect);
|
||||
final float displayWidth = displayRect.width();
|
||||
final float displayHeight = displayRect.height();
|
||||
|
||||
// Compute a transform matrix to undo the coordinate space transformation,
|
||||
// and present the window at the same physical position it previously occupied.
|
||||
final int deltaRotation = DisplayContent.deltaRotation(newRotation, oldRotation);
|
||||
DisplayContent.createRotationMatrix(deltaRotation, x, y, displayWidth, displayHeight,
|
||||
transformToRotation(oldRotation, newRotation, w.mFrame.width(), w.mFrame.height(),
|
||||
transform);
|
||||
|
||||
// We just need to apply a rotation matrix to the window. For example
|
||||
// if we have a portrait window and rotate to landscape, the window is still portrait
|
||||
// and now extends off the bottom of the screen (and only halfway across). Essentially we
|
||||
// apply a transform to display the current buffer at it's old position
|
||||
// (in the new coordinate space). We then freeze layer updates until the resize
|
||||
// occurs, at which point we undo, them.
|
||||
mService.markForSeamlessRotation(w, true);
|
||||
transform.getValues(mService.mTmpFloats);
|
||||
|
||||
float DsDx = mService.mTmpFloats[Matrix.MSCALE_X];
|
||||
|
||||
@@ -270,12 +270,6 @@ class WindowToken extends WindowContainer<WindowState> {
|
||||
dc.reParentWindowToken(this);
|
||||
mDisplayContent = dc;
|
||||
|
||||
// The rounded corner overlay should not be rotated. We ensure that by moving it outside
|
||||
// the windowing layer.
|
||||
if (mRoundedCornerOverlay) {
|
||||
mDisplayContent.reparentToOverlay(mPendingTransaction, mSurfaceControl);
|
||||
}
|
||||
|
||||
// TODO(b/36740756): One day this should perhaps be hooked
|
||||
// up with goodToGo, so we don't move a window
|
||||
// to another display before the window behind
|
||||
|
||||
@@ -22,7 +22,11 @@ import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
|
||||
import android.annotation.Dimension;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.Surface.Rotation;
|
||||
|
||||
public class CoordinateTransforms {
|
||||
@@ -59,4 +63,93 @@ public class CoordinateTransforms {
|
||||
throw new IllegalArgumentException("Unknown rotation: " + rotation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a matrix such that given a rotation, it transforms that rotation's logical coordinates
|
||||
* to physical coordinates.
|
||||
*
|
||||
* @param rotation the rotation to which the matrix should transform
|
||||
* @param out the matrix to be set
|
||||
*/
|
||||
public static void transformLogicalToPhysicalCoordinates(@Rotation int rotation,
|
||||
@Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
|
||||
switch (rotation) {
|
||||
case ROTATION_0:
|
||||
out.reset();
|
||||
break;
|
||||
case ROTATION_90:
|
||||
out.setRotate(90);
|
||||
out.preTranslate(0, -physicalWidth);
|
||||
break;
|
||||
case ROTATION_180:
|
||||
out.setRotate(180);
|
||||
out.preTranslate(-physicalWidth, -physicalHeight);
|
||||
break;
|
||||
case ROTATION_270:
|
||||
out.setRotate(270);
|
||||
out.preTranslate(-physicalHeight, 0);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown rotation: " + rotation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a matrix such that given a two rotations, that it transforms coordinates given in the
|
||||
* old rotation to coordinates that refer to the same physical location in the new rotation.
|
||||
*
|
||||
* @param oldRotation the rotation to transform from
|
||||
* @param newRotation the rotation to transform to
|
||||
* @param info the display info
|
||||
* @param out a matrix that will be set to the transform
|
||||
*/
|
||||
public static void transformToRotation(@Rotation int oldRotation,
|
||||
@Rotation int newRotation, DisplayInfo info, Matrix out) {
|
||||
final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
|
||||
final int h = flipped ? info.logicalWidth : info.logicalHeight;
|
||||
final int w = flipped ? info.logicalHeight : info.logicalWidth;
|
||||
|
||||
final Matrix tmp = new Matrix();
|
||||
transformLogicalToPhysicalCoordinates(oldRotation, w, h, out);
|
||||
transformPhysicalToLogicalCoordinates(newRotation, w, h, tmp);
|
||||
out.postConcat(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a matrix such that given a two rotations, that it transforms coordinates given in the
|
||||
* old rotation to coordinates that refer to the same physical location in the new rotation.
|
||||
*
|
||||
* @param oldRotation the rotation to transform from
|
||||
* @param newRotation the rotation to transform to
|
||||
* @param newWidth the width of the area to transform, in the new rotation
|
||||
* @param newHeight the height of the area to transform, in the new rotation
|
||||
* @param out a matrix that will be set to the transform
|
||||
*/
|
||||
public static void transformToRotation(@Rotation int oldRotation,
|
||||
@Rotation int newRotation, int newWidth, int newHeight, Matrix out) {
|
||||
final boolean flipped = newRotation == ROTATION_90 || newRotation == ROTATION_270;
|
||||
final int h = flipped ? newWidth : newHeight;
|
||||
final int w = flipped ? newHeight : newWidth;
|
||||
|
||||
final Matrix tmp = new Matrix();
|
||||
transformLogicalToPhysicalCoordinates(oldRotation, w, h, out);
|
||||
transformPhysicalToLogicalCoordinates(newRotation, w, h, tmp);
|
||||
out.postConcat(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a rect using a transformation matrix
|
||||
*
|
||||
* @param transform the transformation to apply to the rect
|
||||
* @param inOutRect the rect to transform
|
||||
* @param tmp a temporary value, if null the function will allocate its own.
|
||||
*/
|
||||
public static void transformRect(Matrix transform, Rect inOutRect, @Nullable RectF tmp) {
|
||||
if (tmp == null) {
|
||||
tmp = new RectF();
|
||||
}
|
||||
tmp.set(inOutRect);
|
||||
transform.mapRect(tmp);
|
||||
inOutRect.set((int) tmp.left, (int) tmp.top, (int) tmp.right, (int) tmp.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.wm;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowManager;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -29,6 +31,8 @@ import java.util.LinkedList;
|
||||
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90;
|
||||
import static android.view.Surface.ROTATION_0;
|
||||
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
@@ -48,8 +52,10 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -349,6 +355,32 @@ public class WindowStateTests extends WindowTestsBase {
|
||||
assertThat(app.getDisplayId(), is(mDisplayContent.getDisplayId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeamlesslyRotateWindow() {
|
||||
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
|
||||
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
|
||||
|
||||
app.mHasSurface = true;
|
||||
app.mSurfaceControl = mock(SurfaceControl.class);
|
||||
app.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
|
||||
try {
|
||||
app.mFrame.set(10, 20, 60, 80);
|
||||
|
||||
app.seamlesslyRotate(t, ROTATION_0, ROTATION_90);
|
||||
|
||||
assertTrue(app.mSeamlesslyRotated);
|
||||
assertEquals(new Rect(20, mDisplayInfo.logicalWidth - 60,
|
||||
80, mDisplayInfo.logicalWidth - 10), app.mFrame);
|
||||
|
||||
verify(t).setPosition(app.mSurfaceControl, app.mFrame.left, app.mFrame.top);
|
||||
verify(app.mWinAnimator.mSurfaceController).setPosition(t, 0, 50, false);
|
||||
verify(app.mWinAnimator.mSurfaceController).setMatrix(t, 0, -1, 1, 0, false);
|
||||
} finally {
|
||||
app.mSurfaceControl = null;
|
||||
app.mHasSurface = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) {
|
||||
reset(mPowerManagerWrapper);
|
||||
final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
|
||||
|
||||
@@ -21,14 +21,19 @@ import static android.view.Surface.ROTATION_180;
|
||||
import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
|
||||
import static com.android.server.wm.utils.CoordinateTransforms.transformLogicalToPhysicalCoordinates;
|
||||
import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
|
||||
|
||||
|
||||
import static com.android.server.wm.utils.CoordinateTransforms.transformToRotation;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.view.DisplayInfo;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -41,6 +46,7 @@ public class CoordinateTransformsTest {
|
||||
private static final int H = 400;
|
||||
|
||||
private final Matrix mMatrix = new Matrix();
|
||||
private final Matrix mMatrix2 = new Matrix();
|
||||
|
||||
@Rule
|
||||
public final ErrorCollector mErrorCollector = new ErrorCollector();
|
||||
@@ -48,39 +54,140 @@ public class CoordinateTransformsTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mMatrix.setTranslate(0xdeadbeef, 0xdeadbeef);
|
||||
mMatrix2.setTranslate(0xbeefdead, 0xbeefdead);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformPhysicalToLogicalCoordinates_rot0() throws Exception {
|
||||
public void transformPhysicalToLogicalCoordinates_rot0() {
|
||||
transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix);
|
||||
assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformPhysicalToLogicalCoordinates_rot90() throws Exception {
|
||||
public void transformPhysicalToLogicalCoordinates_rot90() {
|
||||
transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix);
|
||||
|
||||
checkDevicePoint(0, 0).mapsToLogicalPoint(0, W);
|
||||
checkDevicePoint(W, H).mapsToLogicalPoint(H, 0);
|
||||
checkPoint(0, 0).transformsTo(0, W);
|
||||
checkPoint(W, H).transformsTo(H, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformPhysicalToLogicalCoordinates_rot180() throws Exception {
|
||||
public void transformPhysicalToLogicalCoordinates_rot180() {
|
||||
transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix);
|
||||
|
||||
checkDevicePoint(0, 0).mapsToLogicalPoint(W, H);
|
||||
checkDevicePoint(W, H).mapsToLogicalPoint(0, 0);
|
||||
checkPoint(0, 0).transformsTo(W, H);
|
||||
checkPoint(W, H).transformsTo(0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformPhysicalToLogicalCoordinates_rot270() throws Exception {
|
||||
public void transformPhysicalToLogicalCoordinates_rot270() {
|
||||
transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix);
|
||||
|
||||
checkDevicePoint(0, 0).mapsToLogicalPoint(H, 0);
|
||||
checkDevicePoint(W, H).mapsToLogicalPoint(0, W);
|
||||
checkPoint(0, 0).transformsTo(H, 0);
|
||||
checkPoint(W, H).transformsTo(0, W);
|
||||
}
|
||||
|
||||
private DevicePointAssertable checkDevicePoint(int x, int y) {
|
||||
@Test
|
||||
public void transformLogicalToPhysicalCoordinates_rot0() {
|
||||
transformLogicalToPhysicalCoordinates(ROTATION_0, W, H, mMatrix);
|
||||
assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformLogicalToPhysicalCoordinates_rot90() {
|
||||
transformLogicalToPhysicalCoordinates(ROTATION_90, W, H, mMatrix);
|
||||
|
||||
checkPoint(0, W).transformsTo(0, 0);
|
||||
checkPoint(H, 0).transformsTo(W, H);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformLogicalToPhysicalCoordinates_rot180() {
|
||||
transformLogicalToPhysicalCoordinates(ROTATION_180, W, H, mMatrix);
|
||||
|
||||
checkPoint(W, H).transformsTo(0, 0);
|
||||
checkPoint(0, 0).transformsTo(W, H);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformLogicalToPhysicalCoordinates_rot270() {
|
||||
transformLogicalToPhysicalCoordinates(ROTATION_270, W, H, mMatrix);
|
||||
|
||||
checkPoint(H, 0).transformsTo(0, 0);
|
||||
checkPoint(0, W).transformsTo(W, H);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformLogicalToPhysicalCoordinatesIsInverse_rot0() {
|
||||
transformLogicalToPhysicalCoordinates(ROTATION_0, W, H, mMatrix);
|
||||
transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix2);
|
||||
|
||||
assertMatricesAreInverses(mMatrix, mMatrix2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformLogicalToPhysicalCoordinatesIsInverse_rot90() {
|
||||
transformLogicalToPhysicalCoordinates(ROTATION_90, W, H, mMatrix);
|
||||
transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix2);
|
||||
|
||||
assertMatricesAreInverses(mMatrix, mMatrix2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformLogicalToPhysicalCoordinatesIsInverse_rot180() {
|
||||
transformLogicalToPhysicalCoordinates(ROTATION_180, W, H, mMatrix);
|
||||
transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix2);
|
||||
|
||||
assertMatricesAreInverses(mMatrix, mMatrix2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformLogicalToPhysicalCoordinatesIsInverse_rot270() {
|
||||
transformLogicalToPhysicalCoordinates(ROTATION_270, W, H, mMatrix);
|
||||
transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix2);
|
||||
|
||||
assertMatricesAreInverses(mMatrix, mMatrix2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformBetweenRotations_rot180_rot270() {
|
||||
// W,H are flipped, because they need to be given in the new orientation, i.e. ROT_270.
|
||||
transformToRotation(ROTATION_180, ROTATION_270, H, W, mMatrix);
|
||||
|
||||
checkPoint(0, 0).transformsTo(0, W);
|
||||
checkPoint(W, H).transformsTo(H, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformBetweenRotations_rot90_rot0() {
|
||||
transformToRotation(ROTATION_180, ROTATION_270, W, H, mMatrix);
|
||||
|
||||
checkPoint(0, 0).transformsTo(0, H);
|
||||
// H,W is bottom right in ROT_90
|
||||
checkPoint(H, W).transformsTo(W, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformBetweenRotations_displayInfo() {
|
||||
final DisplayInfo di = new DisplayInfo();
|
||||
di.rotation = ROTATION_90;
|
||||
di.logicalWidth = H; // dimensions are flipped in ROT_90
|
||||
di.logicalHeight = W;
|
||||
transformToRotation(ROTATION_180, ROTATION_270, di, mMatrix);
|
||||
|
||||
// W,H are flipped, because they need to be given in the new orientation, i.e. ROT_270.
|
||||
transformToRotation(ROTATION_180, ROTATION_270, H, W, mMatrix2);
|
||||
|
||||
assertEquals(mMatrix2, mMatrix);
|
||||
}
|
||||
|
||||
private void assertMatricesAreInverses(Matrix matrix, Matrix matrix2) {
|
||||
final Matrix concat = new Matrix();
|
||||
concat.setConcat(matrix, matrix2);
|
||||
assertTrue("expected identity, but was: " + concat, concat.isIdentity());
|
||||
}
|
||||
|
||||
private TransformPointAssertable checkPoint(int x, int y) {
|
||||
final Point devicePoint = new Point(x, y);
|
||||
final float[] fs = new float[] {x, y};
|
||||
mMatrix.mapPoints(fs);
|
||||
@@ -92,7 +199,7 @@ public class CoordinateTransformsTest {
|
||||
};
|
||||
}
|
||||
|
||||
public interface DevicePointAssertable {
|
||||
void mapsToLogicalPoint(int x, int y);
|
||||
public interface TransformPointAssertable {
|
||||
void transformsTo(int x, int y);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user