Merge "ScreenDecorations: Fix stuck / delayed rotations" into pi-dev
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 194679c..71cf9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -39,10 +39,13 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.SystemProperties;
import android.provider.Settings.Secure;
import android.support.annotation.VisibleForTesting;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
@@ -57,6 +60,7 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
+import com.android.internal.util.Preconditions;
import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
@@ -74,6 +78,9 @@
* for antialiasing and emulation purposes.
*/
public class ScreenDecorations extends SystemUI implements Tunable {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "ScreenDecorations";
+
public static final String SIZE = "sysui_rounded_size";
public static final String PADDING = "sysui_rounded_content_padding";
private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
@@ -93,9 +100,24 @@
private DisplayCutoutView mCutoutTop;
private DisplayCutoutView mCutoutBottom;
private boolean mPendingRotationChange;
+ private Handler mHandler;
@Override
public void start() {
+ mHandler = startHandlerThread();
+ mHandler.post(this::startOnScreenDecorationsThread);
+ setupStatusBarPaddingIfNeeded();
+ }
+
+ @VisibleForTesting
+ Handler startHandlerThread() {
+ HandlerThread thread = new HandlerThread("ScreenDecorations");
+ thread.start();
+ return thread.getThreadHandler();
+ }
+
+ private void startOnScreenDecorationsThread() {
+ mRotation = RotationUtils.getExactRotation(mContext);
mWindowManager = mContext.getSystemService(WindowManager.class);
mRoundedDefault = mContext.getResources().getDimensionPixelSize(
R.dimen.rounded_corner_radius);
@@ -107,12 +129,6 @@
setupDecorations();
}
- int padding = mContext.getResources().getDimensionPixelSize(
- R.dimen.rounded_corner_content_padding);
- if (padding != 0) {
- setupPadding(padding);
- }
-
mDisplayListener = new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
@@ -126,8 +142,8 @@
@Override
public void onDisplayChanged(int displayId) {
- if (mOverlay != null && mBottomOverlay != null
- && mRotation != RotationUtils.getExactRotation(mContext)) {
+ final int newRotation = RotationUtils.getExactRotation(mContext);
+ if (mOverlay != null && mBottomOverlay != null && mRotation != newRotation) {
// 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
@@ -136,20 +152,24 @@
// - 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));
+ if (DEBUG) {
+ Log.i(TAG, "Rotation changed, deferring " + newRotation + ", staying at "
+ + mRotation);
+ }
+ mOverlay.getViewTreeObserver().addOnPreDrawListener(
+ new RestartingPreDrawListener(mOverlay, newRotation));
+ mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
+ new RestartingPreDrawListener(mBottomOverlay, newRotation));
}
updateOrientation();
}
};
- mRotation = -1;
mDisplayManager = (DisplayManager) mContext.getSystemService(
Context.DISPLAY_SERVICE);
- mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+ updateOrientation();
}
private void setupDecorations() {
@@ -179,10 +199,11 @@
mWindowManager.getDefaultDisplay().getMetrics(metrics);
mDensity = metrics.density;
- Dependency.get(TunerService.class).addTunable(this, SIZE);
+ Dependency.get(Dependency.MAIN_HANDLER).post(
+ () -> Dependency.get(TunerService.class).addTunable(this, SIZE));
// Watch color inversion and invert the overlay as needed.
- SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER),
+ SecureSetting setting = new SecureSetting(mContext, mHandler,
Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
@@ -215,18 +236,37 @@
.start();
}
});
+
+ mOverlay.getViewTreeObserver().addOnPreDrawListener(
+ new ValidatingPreDrawListener(mOverlay));
+ mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
+ new ValidatingPreDrawListener(mBottomOverlay));
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
- mPendingRotationChange = false;
- updateOrientation();
- if (shouldDrawCutout() && mOverlay == null) {
- setupDecorations();
- }
+ mHandler.post(() -> {
+ int oldRotation = mRotation;
+ mPendingRotationChange = false;
+ updateOrientation();
+ if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
+ if (shouldDrawCutout() && mOverlay == null) {
+ setupDecorations();
+ }
+ if (mOverlay != null) {
+ // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(),
+ // which ensures that the forced seamless rotation will end, even if we updated
+ // the rotation before window manager was ready (and was still waiting for sending
+ // the updated rotation).
+ updateLayoutParams();
+ }
+ });
}
- protected void updateOrientation() {
+ private void updateOrientation() {
+ Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
+ "must call on " + mHandler.getLooper().getThread()
+ + ", but was " + Thread.currentThread());
if (mPendingRotationChange) {
return;
}
@@ -306,7 +346,19 @@
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
}
- private void setupPadding(int padding) {
+
+ private void setupStatusBarPaddingIfNeeded() {
+ // TODO: This should be moved to a more appropriate place, as it is not related to the
+ // screen decorations overlay.
+ int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.rounded_corner_content_padding);
+ if (padding != 0) {
+ setupStatusBarPadding(padding);
+ }
+
+ }
+
+ private void setupStatusBarPadding(int padding) {
// Add some padding to all the content near the edge of the screen.
StatusBar sb = getComponent(StatusBar.class);
View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
@@ -375,30 +427,32 @@
@Override
public void onTuningChanged(String key, String newValue) {
- if (mOverlay == null) return;
- if (SIZE.equals(key)) {
- int size = mRoundedDefault;
- int sizeTop = mRoundedDefaultTop;
- int sizeBottom = mRoundedDefaultBottom;
- if (newValue != null) {
- try {
- size = (int) (Integer.parseInt(newValue) * mDensity);
- } catch (Exception e) {
+ mHandler.post(() -> {
+ if (mOverlay == null) return;
+ if (SIZE.equals(key)) {
+ int size = mRoundedDefault;
+ int sizeTop = mRoundedDefaultTop;
+ int sizeBottom = mRoundedDefaultBottom;
+ if (newValue != null) {
+ try {
+ size = (int) (Integer.parseInt(newValue) * mDensity);
+ } catch (Exception e) {
+ }
}
- }
- if (sizeTop == 0) {
- sizeTop = size;
- }
- if (sizeBottom == 0) {
- sizeBottom = size;
- }
+ if (sizeTop == 0) {
+ sizeTop = size;
+ }
+ if (sizeBottom == 0) {
+ sizeBottom = size;
+ }
- setSize(mOverlay.findViewById(R.id.left), sizeTop);
- setSize(mOverlay.findViewById(R.id.right), sizeTop);
- setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
- setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
- }
+ setSize(mOverlay.findViewById(R.id.left), sizeTop);
+ setSize(mOverlay.findViewById(R.id.right), sizeTop);
+ setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
+ setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
+ }
+ });
}
private void setSize(View view, int pixelSize) {
@@ -457,6 +511,11 @@
mVisibilityChangedListener = visibilityChangedListener;
mDecorations = decorations;
setId(R.id.display_cutout);
+ if (DEBUG) {
+ getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG,
+ (mInitialStart ? "OverlayTop" : "OverlayBottom")
+ + " drawn in rot " + mRotation));
+ }
}
public void setColor(int color) {
@@ -692,20 +751,66 @@
private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
private final View mView;
+ private final int mTargetRotation;
- private RestartingPreDrawListener(View view) {
+ private RestartingPreDrawListener(View view, int targetRotation) {
+ mView = view;
+ mTargetRotation = targetRotation;
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ mView.getViewTreeObserver().removeOnPreDrawListener(this);
+
+ if (mTargetRotation == mRotation) {
+ if (DEBUG) {
+ Log.i(TAG, (mView == mOverlay ? "OverlayTop" : "OverlayBottom")
+ + " already in target rot "
+ + mTargetRotation + ", allow draw without restarting it");
+ }
+ return true;
+ }
+
+ mPendingRotationChange = false;
+ // This changes the window attributes - we need to restart the traversal for them to
+ // take effect.
+ updateOrientation();
+ if (DEBUG) {
+ Log.i(TAG, (mView == mOverlay ? "OverlayTop" : "OverlayBottom")
+ + " restarting listener fired, restarting draw for rot " + mRotation);
+ }
+ mView.invalidate();
+ return false;
+ }
+ }
+
+ /**
+ * A pre-draw listener, that validates that the rotation we draw in matches the displays
+ * rotation before continuing the draw.
+ *
+ * This is to prevent a race condition, where we have not received the display changed event
+ * yet, and would thus draw in an old orientation.
+ */
+ private class ValidatingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
+
+ private final View mView;
+
+ public ValidatingPreDrawListener(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;
+ final int displayRotation = RotationUtils.getExactRotation(mContext);
+ if (displayRotation != mRotation && !mPendingRotationChange) {
+ if (DEBUG) {
+ Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
+ + displayRotation + ". Restarting draw");
+ }
+ mView.invalidate();
+ return false;
+ }
+ return true;
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index f1bf31d..644c0b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -34,8 +34,10 @@
import android.app.Fragment;
import android.content.res.Configuration;
+import android.os.Handler;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.Display;
import android.view.View;
@@ -60,6 +62,7 @@
@SmallTest
public class ScreenDecorationsTest extends SysuiTestCase {
+ private TestableLooper mTestableLooper;
private ScreenDecorations mScreenDecorations;
private StatusBar mStatusBar;
private WindowManager mWindowManager;
@@ -71,6 +74,10 @@
@Before
public void setup() {
+ mTestableLooper = TestableLooper.get(this);
+ mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
+ new Handler(mTestableLooper.getLooper()));
+
mStatusBar = mock(StatusBar.class);
mWindowManager = mock(WindowManager.class);
mView = spy(new StatusBarWindowView(mContext, null));
@@ -88,7 +95,31 @@
mTunerService = mDependency.injectMockDependency(TunerService.class);
- mScreenDecorations = new ScreenDecorations();
+
+ mScreenDecorations = new ScreenDecorations() {
+ @Override
+ public void start() {
+ super.start();
+ mTestableLooper.processAllMessages();
+ }
+
+ @Override
+ Handler startHandlerThread() {
+ return new Handler(mTestableLooper.getLooper());
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mTestableLooper.processAllMessages();
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ super.onTuningChanged(key, newValue);
+ mTestableLooper.processAllMessages();
+ }
+ };
mScreenDecorations.mContext = mContext;
mScreenDecorations.mComponents = mContext.getComponents();
diff --git a/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java b/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java
index bebc565..efb43a6 100644
--- a/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java
@@ -21,6 +21,7 @@
import android.graphics.Matrix;
import android.view.DisplayInfo;
+import android.view.Surface.Rotation;
import com.android.server.wm.utils.CoordinateTransforms;
@@ -65,6 +66,16 @@
}
/**
+ * Returns the rotation of the display before it started rotating.
+ *
+ * @return the old rotation of the display
+ */
+ @Rotation
+ public int getOldRotation() {
+ return mOldRotation;
+ }
+
+ /**
* Removes the transform to the window token's surface that undoes the effect of the global
* display rotation.
*
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 009f393..1ae680f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -681,6 +681,10 @@
void forceSeamlesslyRotateIfAllowed(int oldRotation, int rotation) {
if (mForceSeamlesslyRotate) {
+ if (mPendingForcedSeamlessRotate != null) {
+ oldRotation = mPendingForcedSeamlessRotate.getOldRotation();
+ }
+
mPendingForcedSeamlessRotate = new ForcedSeamlessRotator(
oldRotation, rotation, getDisplayInfo());
mPendingForcedSeamlessRotate.unrotate(this.mToken);