diff options
| author | 2024-05-24 08:37:58 +0000 | |
|---|---|---|
| committer | 2024-06-24 08:02:15 +0000 | |
| commit | 854416224196ea79b49baefb8604e20112090d08 (patch) | |
| tree | a252815894a9a89a188ef5ef7f2c7a13ef08267a | |
| parent | 36276e8c56cfa124ba7d31a3282478c5c871adae (diff) | |
Add seq to relayout infos
Keep track of the seq of last reported window infos.
Bug: 339380439
Test: atest FrameworksCoreTests:SequenceUtilsTest
Flag: com.android.window.flags.insets_control_seq
Change-Id: I15cab03ac83ecad0ce4fee4334894a0f933c7366
| -rw-r--r-- | core/java/android/util/SequenceUtils.java | 63 | ||||
| -rw-r--r-- | core/java/android/view/InsetsSourceControl.java | 16 | ||||
| -rw-r--r-- | core/java/android/view/InsetsState.java | 17 | ||||
| -rw-r--r-- | core/java/android/window/ClientWindowFrames.java | 9 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/util/SequenceUtilsTest.java | 89 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/WindowState.java | 6 |
6 files changed, 200 insertions, 0 deletions
diff --git a/core/java/android/util/SequenceUtils.java b/core/java/android/util/SequenceUtils.java new file mode 100644 index 000000000000..f833ce314c91 --- /dev/null +++ b/core/java/android/util/SequenceUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 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 android.util; + +/** + * Utilities to manage an info change seq id to ensure the update is in sync between client and + * system server. This should be used for info that can be updated though multiple IPC channel. + * + * To use it: + * 1. The system server should store the current seq as the source of truth, with initializing to + * {@link #getInitSeq}. + * 2. Whenever a newer info needs to be sent to the client side, the system server should first + * update its seq with {@link #getNextSeq}, then send the new info with the new seq to the client. + * 3. On the client side, when receiving a new info, it should only consume it if it is newer than + * the last received info seq by checking {@link #isIncomingSeqNewer}. + * + * @hide + */ +public final class SequenceUtils { + + private SequenceUtils() { + } + + /** + * Returns {@code true} if the incomingSeq is newer than the curSeq. + */ + public static boolean isIncomingSeqNewer(int curSeq, int incomingSeq) { + // Convert to long for comparison. + final long diff = (long) incomingSeq - curSeq; + // If there has been a sufficiently large jump, assume the sequence has wrapped around. + // For example, when the last seq is MAX_VALUE, the incoming seq will be MIN_VALUE + 1. + // diff = MIN_VALUE + 1 - MAX_VALUE. It is smaller than 0, but should be treated as newer. + return diff > 0 || diff < Integer.MIN_VALUE; + } + + /** Returns the initial seq. */ + public static int getInitSeq() { + return Integer.MIN_VALUE; + } + + /** Returns the next seq. */ + public static int getNextSeq(int seq) { + return seq == Integer.MAX_VALUE + // Skip the initial seq, so that when the app process is relaunched, the incoming + // seq from the server is always treated as newer. + ? getInitSeq() + 1 + : ++seq; + } +} diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 487214c5c33a..2efa647d3169 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -18,6 +18,7 @@ package android.view; import static android.graphics.PointProto.X; import static android.graphics.PointProto.Y; +import static android.util.SequenceUtils.getInitSeq; import static android.view.InsetsSourceControlProto.LEASH; import static android.view.InsetsSourceControlProto.POSITION; import static android.view.InsetsSourceControlProto.TYPE_NUMBER; @@ -266,6 +267,9 @@ public class InsetsSourceControl implements Parcelable { private @Nullable InsetsSourceControl[] mControls; + /** To make sure the info update between client and system server is in order. */ + private int mSeq = getInitSeq(); + public Array() { } @@ -280,9 +284,18 @@ public class InsetsSourceControl implements Parcelable { readFromParcel(in); } + public int getSeq() { + return mSeq; + } + + public void setSeq(int seq) { + mSeq = seq; + } + /** Updates the current Array to the given Array. */ public void setTo(@NonNull Array other, boolean copyControls) { set(other.mControls, copyControls); + mSeq = other.mSeq; } /** Updates the current controls to the given controls. */ @@ -336,11 +349,13 @@ public class InsetsSourceControl implements Parcelable { public void readFromParcel(Parcel in) { mControls = in.createTypedArray(InsetsSourceControl.CREATOR); + mSeq = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { out.writeTypedArray(mControls, flags); + out.writeInt(mSeq); } public static final @NonNull Creator<Array> CREATOR = new Creator<>() { @@ -362,6 +377,7 @@ public class InsetsSourceControl implements Parcelable { return false; } final InsetsSourceControl.Array other = (InsetsSourceControl.Array) o; + // mSeq is for internal bookkeeping only. return Arrays.equals(mControls, other.mControls); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 21eec67bfe10..bbd9acfd4cd7 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -17,6 +17,7 @@ package android.view; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.util.SequenceUtils.getInitSeq; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.InsetsStateProto.DISPLAY_CUTOUT; @@ -95,6 +96,9 @@ public class InsetsState implements Parcelable { /** The display shape */ private DisplayShape mDisplayShape = DisplayShape.NONE; + /** To make sure the info update between client and system server is in order. */ + private int mSeq = getInitSeq(); + public InsetsState() { mSources = new SparseArray<>(); } @@ -586,6 +590,14 @@ public class InsetsState implements Parcelable { } } + public int getSeq() { + return mSeq; + } + + public void setSeq(int seq) { + mSeq = seq; + } + public void set(InsetsState other) { set(other, false /* copySources */); } @@ -597,6 +609,7 @@ public class InsetsState implements Parcelable { mRoundedCornerFrame.set(other.mRoundedCornerFrame); mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); mDisplayShape = other.getDisplayShape(); + mSeq = other.mSeq; mSources.clear(); for (int i = 0, size = other.mSources.size(); i < size; i++) { final InsetsSource otherSource = other.mSources.valueAt(i); @@ -620,6 +633,7 @@ public class InsetsState implements Parcelable { mRoundedCornerFrame.set(other.mRoundedCornerFrame); mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); mDisplayShape = other.getDisplayShape(); + mSeq = other.mSeq; if (types == 0) { return; } @@ -705,6 +719,7 @@ public class InsetsState implements Parcelable { || !mRoundedCornerFrame.equals(state.mRoundedCornerFrame) || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds) || !mDisplayShape.equals(state.mDisplayShape)) { + // mSeq is for internal bookkeeping only. return false; } @@ -778,6 +793,7 @@ public class InsetsState implements Parcelable { mRoundedCornerFrame.writeToParcel(dest, flags); dest.writeTypedObject(mPrivacyIndicatorBounds, flags); dest.writeTypedObject(mDisplayShape, flags); + dest.writeInt(mSeq); final int size = mSources.size(); dest.writeInt(size); for (int i = 0; i < size; i++) { @@ -803,6 +819,7 @@ public class InsetsState implements Parcelable { mRoundedCornerFrame.readFromParcel(in); mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR); mDisplayShape = in.readTypedObject(DisplayShape.CREATOR); + mSeq = in.readInt(); final int size = in.readInt(); final SparseArray<InsetsSource> sources; if (mSources == null) { diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index d5398e6268dc..781a9019d1ae 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -16,6 +16,8 @@ package android.window; +import static android.util.SequenceUtils.getInitSeq; + import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; @@ -53,6 +55,9 @@ public class ClientWindowFrames implements Parcelable { public float compatScale = 1f; + /** To make sure the info update between client and system server is in order. */ + public int seq = getInitSeq(); + public ClientWindowFrames() { } @@ -74,6 +79,7 @@ public class ClientWindowFrames implements Parcelable { } isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout; compatScale = other.compatScale; + seq = other.seq; } /** Needed for AIDL out parameters. */ @@ -84,6 +90,7 @@ public class ClientWindowFrames implements Parcelable { attachedFrame = in.readTypedObject(Rect.CREATOR); isParentFrameClippedByDisplayCutout = in.readBoolean(); compatScale = in.readFloat(); + seq = in.readInt(); } @Override @@ -94,6 +101,7 @@ public class ClientWindowFrames implements Parcelable { dest.writeTypedObject(attachedFrame, flags); dest.writeBoolean(isParentFrameClippedByDisplayCutout); dest.writeFloat(compatScale); + dest.writeInt(seq); } @Override @@ -116,6 +124,7 @@ public class ClientWindowFrames implements Parcelable { return false; } final ClientWindowFrames other = (ClientWindowFrames) o; + // seq is for internal bookkeeping only. return frame.equals(other.frame) && displayFrame.equals(other.displayFrame) && parentFrame.equals(other.parentFrame) diff --git a/core/tests/coretests/src/android/util/SequenceUtilsTest.java b/core/tests/coretests/src/android/util/SequenceUtilsTest.java new file mode 100644 index 000000000000..020520dbcf85 --- /dev/null +++ b/core/tests/coretests/src/android/util/SequenceUtilsTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024 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 android.util; + + +import static android.util.SequenceUtils.getInitSeq; +import static android.util.SequenceUtils.getNextSeq; +import static android.util.SequenceUtils.isIncomingSeqNewer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.DisabledOnRavenwood; +import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for subtypes of {@link SequenceUtils}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:SequenceUtilsTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +@DisabledOnRavenwood(blockedBy = SequenceUtils.class) +public class SequenceUtilsTest { + + // This is needed to disable the test in Ravenwood test, because SequenceUtils hasn't opted in + // for Ravenwood, which is still in experiment. + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test + public void testNextSeq() { + assertEquals(getInitSeq() + 1, getNextSeq(getInitSeq())); + assertEquals(getInitSeq() + 1, getNextSeq(Integer.MAX_VALUE)); + } + + @Test + public void testIsIncomingSeqNewer() { + assertTrue(isIncomingSeqNewer(getInitSeq() + 1, getInitSeq() + 10)); + assertFalse(isIncomingSeqNewer(getInitSeq() + 10, getInitSeq() + 1)); + assertTrue(isIncomingSeqNewer(-100, 100)); + assertFalse(isIncomingSeqNewer(100, -100)); + assertTrue(isIncomingSeqNewer(1, 2)); + assertFalse(isIncomingSeqNewer(2, 1)); + + // Possible incoming seq are all newer than the initial seq. + assertTrue(isIncomingSeqNewer(getInitSeq(), getInitSeq() + 1)); + assertTrue(isIncomingSeqNewer(getInitSeq(), -100)); + assertTrue(isIncomingSeqNewer(getInitSeq(), 0)); + assertTrue(isIncomingSeqNewer(getInitSeq(), 100)); + assertTrue(isIncomingSeqNewer(getInitSeq(), Integer.MAX_VALUE)); + assertTrue(isIncomingSeqNewer(getInitSeq(), getNextSeq(Integer.MAX_VALUE))); + + // False for the same seq. + assertFalse(isIncomingSeqNewer(getInitSeq(), getInitSeq())); + assertFalse(isIncomingSeqNewer(100, 100)); + assertFalse(isIncomingSeqNewer(Integer.MAX_VALUE, Integer.MAX_VALUE)); + + // True when there is a large jump (overflow). + assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getInitSeq() + 1)); + assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getInitSeq() + 100)); + assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getNextSeq(Integer.MAX_VALUE))); + } +} diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index dcd4bd68c3fc..9e4cc152d022 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -96,6 +96,7 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; +import static android.util.SequenceUtils.getNextSeq; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; @@ -3652,6 +3653,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } outFrames.compatScale = getCompatScaleForClient(); + outFrames.seq = getNextSeq(mLastReportedFrames.seq); if (mLastReportedFrames != outFrames) { mLastReportedFrames.setTo(outFrames); } @@ -3682,7 +3684,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void fillInsetsState(@NonNull InsetsState outInsetsState, boolean copySources) { + final int lastSeq = mLastReportedInsetsState.getSeq(); outInsetsState.set(getCompatInsetsState(), copySources); + outInsetsState.setSeq(getNextSeq(lastSeq)); if (outInsetsState != mLastReportedInsetsState) { // No need to copy for the recorded. mLastReportedInsetsState.set(outInsetsState, false /* copySources */); @@ -3691,9 +3695,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void fillInsetsSourceControls(@NonNull InsetsSourceControl.Array outArray, boolean copyControls) { + final int lastSeq = mLastReportedInsetsState.getSeq(); final InsetsSourceControl[] controls = getDisplayContent().getInsetsStateController().getControlsForDispatch(this); outArray.set(controls, copyControls); + outArray.setSeq(getNextSeq(lastSeq)); if (outArray != mLastReportedActiveControls) { // No need to copy for the recorded. mLastReportedActiveControls.setTo(outArray, false /* copyControls */); |