Merge "Make caption a insets source" into rvc-dev am: ac30aafd29

Change-Id: I5c486a30fe0828cfef21e2804a6dd9e3c4415d85
This commit is contained in:
Jorim Jaggi
2020-03-27 17:42:41 +00:00
committed by Automerger Merge Worker
13 changed files with 184 additions and 46 deletions

View File

@@ -16,6 +16,7 @@
package android.view;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.toInternalType;
import static android.view.InsetsState.toPublicType;
@@ -367,6 +368,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private int mLastLegacySystemUiFlags;
private DisplayCutout mLastDisplayCutout;
private boolean mStartingAnimation;
private int mCaptionInsetsHeight = 0;
private SyncRtSurfaceTransactionApplier mApplier;
@@ -460,7 +462,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@VisibleForTesting
public boolean onStateChanged(InsetsState state) {
boolean localStateChanged = !mState.equals(state);
boolean localStateChanged = !mState.equals(state, true /* excludingCaptionInsets */)
|| !captionInsetsUnchanged();
if (!localStateChanged && mLastDispachedState.equals(state)) {
return false;
}
@@ -470,7 +473,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (localStateChanged) {
mViewRoot.notifyInsetsChanged();
}
if (!mState.equals(mLastDispachedState)) {
if (!mState.equals(mLastDispachedState, true /* excludingCaptionInsets */)) {
sendStateToWindowManager();
}
return true;
@@ -488,6 +491,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
mState.removeSource(source.getType());
}
}
if (mCaptionInsetsHeight != 0) {
mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top,
mFrame.right, mFrame.top + mCaptionInsetsHeight));
}
}
private boolean captionInsetsUnchanged() {
if (mState.peekSource(ITYPE_CAPTION_BAR) == null
&& mCaptionInsetsHeight == 0) {
return true;
}
if (mState.peekSource(ITYPE_CAPTION_BAR) != null
&& mCaptionInsetsHeight
== mState.peekSource(ITYPE_CAPTION_BAR).getFrame().height()) {
return true;
}
return false;
}
/**
@@ -964,6 +984,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
InsetsState tmpState = new InsetsState();
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
if (consumer.getType() == ITYPE_CAPTION_BAR) continue;
if (consumer.getControl() != null) {
tmpState.addSource(mState.getSource(consumer.getType()));
}
@@ -1104,6 +1125,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
return mViewRoot.mWindowAttributes.insetsFlags.appearance;
}
@Override
public void setCaptionInsetsHeight(int height) {
mCaptionInsetsHeight = height;
}
@Override
public void setSystemBarsBehavior(@Behavior int behavior) {
mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;

View File

@@ -16,6 +16,7 @@
package android.view;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import android.annotation.NonNull;
@@ -118,6 +119,12 @@ public class InsetsSource implements Parcelable {
if (!getIntersection(frame, relativeFrame, mTmpFrame)) {
return Insets.NONE;
}
// During drag-move and drag-resizing, the caption insets position may not get updated
// before the app frame get updated. To layout the app content correctly during drag events,
// we always return the insets with the corresponding height covering the top.
if (getType() == ITYPE_CAPTION_BAR) {
return Insets.of(0, frame.height(), 0, 0);
}
// TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout.
// However, we should let the policy decide from the server.

View File

@@ -45,6 +45,8 @@ import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -498,6 +500,19 @@ public class InsetsState implements Parcelable {
@Override
public boolean equals(Object o) {
return equals(o, false);
}
/**
* An equals method can exclude the caption insets. This is useful because we assemble the
* caption insets information on the client side, and when we communicate with server, it's
* excluded.
* @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
* ignore the caption insets source value.
* @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
*/
@VisibleForTesting
public boolean equals(Object o, boolean excludingCaptionInsets) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
@@ -506,11 +521,24 @@ public class InsetsState implements Parcelable {
if (!mDisplayFrame.equals(state.mDisplayFrame)) {
return false;
}
if (mSources.size() != state.mSources.size()) {
int size = mSources.size();
int otherSize = state.mSources.size();
if (excludingCaptionInsets) {
if (mSources.get(ITYPE_CAPTION_BAR) != null) {
size--;
}
if (state.mSources.get(ITYPE_CAPTION_BAR) != null) {
otherSize--;
}
}
if (size != otherSize) {
return false;
}
for (int i = mSources.size() - 1; i >= 0; i--) {
InsetsSource source = mSources.valueAt(i);
if (excludingCaptionInsets) {
if (source.getType() == ITYPE_CAPTION_BAR) continue;
}
InsetsSource otherSource = state.mSources.get(source.getType());
if (otherSource == null) {
return false;

View File

@@ -42,6 +42,7 @@ public class PendingInsetsController implements WindowInsetsController {
private InsetsController mReplayedInsetsController;
private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
@Override
public void show(int types) {
@@ -79,6 +80,11 @@ public class PendingInsetsController implements WindowInsetsController {
return mAppearance;
}
@Override
public void setCaptionInsetsHeight(int height) {
mCaptionInsetsHeight = height;
}
@Override
public void setSystemBarsBehavior(int behavior) {
if (mReplayedInsetsController != null) {
@@ -134,6 +140,9 @@ public class PendingInsetsController implements WindowInsetsController {
if (mAppearanceMask != 0) {
controller.setSystemBarsAppearance(mAppearance, mAppearanceMask);
}
if (mCaptionInsetsHeight != 0) {
controller.setCaptionInsetsHeight(mCaptionInsetsHeight);
}
int size = mRequests.size();
for (int i = 0; i < size; i++) {
mRequests.get(i).replay(controller);

View File

@@ -28768,6 +28768,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
final Rect mStableInsets = new Rect();
/**
* Current caption insets to the display coordinate.
*/
final Rect mCaptionInsets = new Rect();
final DisplayCutout.ParcelableWrapper mDisplayCutout =
new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT);

View File

@@ -158,6 +158,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.DecorView;
import com.android.internal.policy.PhoneFallbackEventHandler;
import com.android.internal.util.Preconditions;
import com.android.internal.view.BaseSurfaceHolder;
@@ -2221,6 +2222,19 @@ public final class ViewRootImpl implements ViewParent,
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
private boolean updateCaptionInsets() {
if (!(mView instanceof DecorView)) return false;
final int captionInsetsHeight = ((DecorView) mView).getCaptionInsetsHeight();
final Rect captionFrame = new Rect();
if (captionInsetsHeight != 0) {
captionFrame.set(mWinFrame.left, mWinFrame.top, mWinFrame.right,
mWinFrame.top + captionInsetsHeight);
}
if (mAttachInfo.mCaptionInsets.equals(captionFrame)) return false;
mAttachInfo.mCaptionInsets.set(captionFrame);
return true;
}
private boolean shouldDispatchCutout() {
return mWindowAttributes.layoutInDisplayCutoutMode
== LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
@@ -2592,6 +2606,9 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars;
dispatchApplyInsets = true;
}
if (updateCaptionInsets()) {
dispatchApplyInsets = true;
}
if (dispatchApplyInsets || mLastSystemUiVisibility !=
mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) {
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;

View File

@@ -49,9 +49,6 @@ import android.transition.Transition;
import android.transition.TransitionManager;
import android.util.Pair;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowInsets.Side.InsetsSide;
import android.view.WindowInsets.Type.InsetsType;
import android.view.accessibility.AccessibilityEvent;
import java.util.Collections;
@@ -323,7 +320,7 @@ public abstract class Window {
@UnsupportedAppUsage
private boolean mDestroyed;
private boolean mOverlayWithDecorCaptionEnabled = false;
private boolean mOverlayWithDecorCaptionEnabled = true;
private boolean mCloseOnSwipeEnabled = false;
// The current window attributes.

View File

@@ -17,6 +17,7 @@
package android.view;
import static android.view.WindowInsets.Type.CAPTION_BAR;
import static android.view.WindowInsets.Type.DISPLAY_CUTOUT;
import static android.view.WindowInsets.Type.FIRST;
import static android.view.WindowInsets.Type.IME;

View File

@@ -195,6 +195,15 @@ public interface WindowInsetsController {
*/
@Appearance int getSystemBarsAppearance();
/**
* Notify the caption insets height change. The information will be used on the client side to,
* make sure the InsetsState has the correct caption insets.
*
* @param height the height of caption bar insets.
* @hide
*/
void setCaptionInsetsHeight(int height);
/**
* Controls the behavior of system bars.
*

View File

@@ -78,7 +78,6 @@ import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.InputQueue;
import android.view.InsetsState;
import android.view.InsetsController;
import android.view.InsetsState.InternalInsetsType;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
@@ -1174,6 +1173,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
animate && !disallowAnimate,
mForceWindowDrawsBarBackgrounds, state);
if (mHasCaption) {
final int captionColor = calculateStatusBarColor();
mDecorCaptionView.getCaption().setBackgroundColor(captionColor);
updateDecorCaptionShade();
}
}
// When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
@@ -1355,7 +1360,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
: state.attributes.isPresent(insetsState, mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
boolean showView = show && !isResizing() && size > 0;
boolean showView = show && !isResizing() && !mHasCaption && size > 0;
boolean visibilityChanged = false;
View view = state.view;
@@ -2021,6 +2026,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
if (getForeground() != null) {
drawableChanged();
}
getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight());
}
}
@@ -2094,6 +2100,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
mDecorCaptionView.onConfigurationChanged(displayWindowDecor);
enableCaption(displayWindowDecor);
}
getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight());
}
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
@@ -2182,11 +2189,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
inflater = inflater.from(context);
final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
null);
setDecorCaptionShade(context, view);
setDecorCaptionShade(view);
return view;
}
private void setDecorCaptionShade(Context context, DecorCaptionView view) {
private void setDecorCaptionShade(DecorCaptionView view) {
final int shade = mWindow.getDecorCaptionShade();
switch (shade) {
case DECOR_CAPTION_SHADE_LIGHT:
@@ -2196,15 +2203,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
setDarkDecorCaptionShade(view);
break;
default: {
TypedValue value = new TypedValue();
context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
// We invert the shade depending on brightness of the theme. Dark shade for light
// theme and vice versa. Thanks to this the buttons should be visible on the
// background.
if (Color.luminance(value.data) < 0.5) {
setLightDecorCaptionShade(view);
} else {
if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) {
setDarkDecorCaptionShade(view);
} else {
setLightDecorCaptionShade(view);
}
break;
}
@@ -2213,7 +2215,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
void updateDecorCaptionShade() {
if (mDecorCaptionView != null) {
setDecorCaptionShade(getContext(), mDecorCaptionView);
setDecorCaptionShade(mDecorCaptionView);
}
}
@@ -2483,6 +2485,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0;
}
/**
* @hide
* @return the height of insets covering the top of window content area.
*/
public int getCaptionInsetsHeight() {
if (!mWindow.isOverlayWithDecorCaptionEnabled()) return 0;
return getCaptionHeight();
}
/**
* Converts a DIP measure into physical pixels.
* @param dip The dip value.

View File

@@ -17,7 +17,6 @@
package com.android.internal.widget;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.AttributeSet;
@@ -53,8 +52,7 @@ import java.util.ArrayList;
* <li>..</li>
* </ul>
*
* Although this ViewGroup has only two direct sub-Views, its behavior is more complex due to
* overlaying caption on the content and drawing.
* Here describe the behavior of overlaying caption on the content and drawing.
*
* First, no matter where the content View gets added, it will always be the first child and the
* caption will be the second. This way the caption will always be drawn on top of the content when
@@ -66,11 +64,9 @@ import java.util.ArrayList;
* <li>DecorCaptionView.onInterceptTouchEvent() will try intercepting the touch events if the
* down action is performed on top close or maximize buttons; the reason for that is we want these
* buttons to always work.</li>
* <li>The content View will receive the touch event. Mind that content is actually underneath the
* caption, so we need to introduce our own dispatch ordering. We achieve this by overriding
* {@link #buildTouchDispatchChildList()}.</li>
* <li>If the touch event is not consumed by the content View, it will go to the caption View
* and the dragging logic will be executed.</li>
* <li>The caption view will try to consume the event to apply the dragging logic.</li>
* <li>If the touch event is not consumed by the caption, the content View will receive the touch
* event</li>
* </ul>
*/
public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
@@ -137,11 +133,6 @@ public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
mOwner = owner;
mShow = show;
mOverlayWithAppContent = owner.isOverlayWithDecorCaptionEnabled();
if (mOverlayWithAppContent) {
// The caption is covering the content, so we make its background transparent to make
// the content visible.
mCaption.setBackgroundColor(Color.TRANSPARENT);
}
updateCaptionVisibility();
// By changing the outline provider to BOUNDS, the window can remove its
// background without removing the shadow.
@@ -235,18 +226,6 @@ public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
return mDragging || mCheckForDragging;
}
@Override
public ArrayList<View> buildTouchDispatchChildList() {
mTouchDispatchList.ensureCapacity(3);
if (mCaption != null) {
mTouchDispatchList.add(mCaption);
}
if (mContent != null) {
mTouchDispatchList.add(mContent);
}
return mTouchDispatchList;
}
@Override
public boolean shouldDelayChildPressedState() {
return false;

View File

@@ -21,6 +21,7 @@ import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsSourceConsumer.ShowResult.IME_SHOW_DELAYED;
import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -88,7 +89,6 @@ import java.util.concurrent.CountDownLatch;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class InsetsControllerTest {
private InsetsController mController;
private SurfaceSession mSession = new SurfaceSession();
private SurfaceControl mLeash;
@@ -665,6 +665,26 @@ public class InsetsControllerTest {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@Test
public void testCaptionInsetsStateAssemble() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.onFrameChanged(new Rect(0, 0, 100, 300));
final InsetsState state = new InsetsState(mController.getState(), true);
final Rect captionFrame = new Rect(0, 0, 100, 100);
mController.setCaptionInsetsHeight(100);
mController.onStateChanged(state);
final InsetsState currentState = new InsetsState(mController.getState());
// The caption bar source should be synced with the info in mAttachInfo.
assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame());
assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/));
mController.setCaptionInsetsHeight(0);
mController.onStateChanged(state);
// The caption bar source should not be there at all, because we don't add empty
// caption to the state from the server.
assertNull(mController.getState().peekSource(ITYPE_CAPTION_BAR));
});
}
private void waitUntilNextFrame() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT,

View File

@@ -153,6 +153,35 @@ public class InsetsStateTest {
}
}
@Test
public void testCalculateInsets_captionStatusBarOverlap() throws Exception {
try (InsetsModeSession session =
new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
Rect visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 100, 400), SOFT_INPUT_ADJUST_NOTHING);
assertEquals(new Rect(0, 300, 0, 0), visibleInsets);
}
}
@Test
public void testCalculateInsets_captionBarOffset() throws Exception {
try (InsetsModeSession session =
new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
Rect visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 150, 400), SOFT_INPUT_ADJUST_NOTHING);
assertEquals(new Rect(0, 300, 0, 0), visibleInsets);
}
}
@Test
public void testStripForDispatch() {
mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));