Loading core/java/android/util/SequenceUtils.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * 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; } } core/java/android/view/InsetsSourceControl.java +16 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { } Loading @@ -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. */ Loading Loading @@ -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<>() { Loading @@ -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); } Loading core/java/android/view/InsetsState.java +17 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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<>(); } Loading Loading @@ -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 */); } Loading @@ -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); Loading @@ -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; } Loading Loading @@ -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; } Loading Loading @@ -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++) { Loading @@ -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) { Loading core/java/android/window/ClientWindowFrames.java +9 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { } Loading @@ -74,6 +79,7 @@ public class ClientWindowFrames implements Parcelable { } isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout; compatScale = other.compatScale; seq = other.seq; } /** Needed for AIDL out parameters. */ Loading @@ -84,6 +90,7 @@ public class ClientWindowFrames implements Parcelable { attachedFrame = in.readTypedObject(Rect.CREATOR); isParentFrameClippedByDisplayCutout = in.readBoolean(); compatScale = in.readFloat(); seq = in.readInt(); } @Override Loading @@ -94,6 +101,7 @@ public class ClientWindowFrames implements Parcelable { dest.writeTypedObject(attachedFrame, flags); dest.writeBoolean(isParentFrameClippedByDisplayCutout); dest.writeFloat(compatScale); dest.writeInt(seq); } @Override Loading @@ -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) Loading core/tests/coretests/src/android/util/SequenceUtilsTest.java 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * 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))); } } Loading
core/java/android/util/SequenceUtils.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * 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; } }
core/java/android/view/InsetsSourceControl.java +16 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { } Loading @@ -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. */ Loading Loading @@ -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<>() { Loading @@ -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); } Loading
core/java/android/view/InsetsState.java +17 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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<>(); } Loading Loading @@ -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 */); } Loading @@ -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); Loading @@ -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; } Loading Loading @@ -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; } Loading Loading @@ -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++) { Loading @@ -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) { Loading
core/java/android/window/ClientWindowFrames.java +9 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { } Loading @@ -74,6 +79,7 @@ public class ClientWindowFrames implements Parcelable { } isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout; compatScale = other.compatScale; seq = other.seq; } /** Needed for AIDL out parameters. */ Loading @@ -84,6 +90,7 @@ public class ClientWindowFrames implements Parcelable { attachedFrame = in.readTypedObject(Rect.CREATOR); isParentFrameClippedByDisplayCutout = in.readBoolean(); compatScale = in.readFloat(); seq = in.readInt(); } @Override Loading @@ -94,6 +101,7 @@ public class ClientWindowFrames implements Parcelable { dest.writeTypedObject(attachedFrame, flags); dest.writeBoolean(isParentFrameClippedByDisplayCutout); dest.writeFloat(compatScale); dest.writeInt(seq); } @Override Loading @@ -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) Loading
core/tests/coretests/src/android/util/SequenceUtilsTest.java 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * 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))); } }