Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8bec6000 authored by Shivam Agrawal's avatar Shivam Agrawal
Browse files

Filter-Diff Screen Layout Changes for Activity Relaunch

Currently, an activity is relaunched from a resize only if
the activity does not handle SCREEN_SIZE config changes and
the new activity size crosses a width, height, or smallest
width resource qualifier. A change in activity size may also
change an activity’s screen layout. However, an activity
is relaunched from a screen layout change if it does not
handle SCREEN_LAYOUT config changes even if the screen layout
did not cross a screen layout resource qualifier.

This CL does three things:
(1) Propogates screen layout qualifiers through the same
path as width, height, and smallest width qualifiers
in the AssetManager to make it available to the
WindowManager.
(2) Prevents an activity relaunch if the screen layout
has been changed but does not cross a screen layout
qualifier.
(3) Adds tests for SizeConfigurationBuckets for the new
screen layout logic as well as for existing logic.

Test: atest FrameworksMockingCoreTests:SizeConfigurationBucketsTest
Bug: b/192369163 b/187529743
Change-Id: I41d28e6492b76c4284c4dca2c1f3f5904fc5e91a
parent bebfe779
Loading
Loading
Loading
Loading
+145 −11
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.window;

import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;

@@ -25,6 +26,7 @@ import android.content.res.Configuration;
import android.os.Parcelable;
import android.util.SparseIntArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;

import java.util.Arrays;
@@ -54,10 +56,24 @@ public final class SizeConfigurationBuckets implements Parcelable {
    @Nullable
    private final int[] mSmallest;

    /** Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets */
    @Nullable
    private final int[] mScreenLayoutSize;

    /**
     * Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a
     * value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and
     * SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change.
     */
    private final boolean mScreenLayoutLongSet;

    public SizeConfigurationBuckets(Configuration[] sizeConfigurations) {
        SparseIntArray horizontal = new SparseIntArray();
        SparseIntArray vertical = new SparseIntArray();
        SparseIntArray smallest = new SparseIntArray();
        SparseIntArray screenLayoutSize = new SparseIntArray();
        int curScreenLayoutSize;
        boolean screenLayoutLongSet = false;
        for (int i = sizeConfigurations.length - 1; i >= 0; i--) {
            Configuration config = sizeConfigurations[i];
            if (config.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
@@ -69,10 +85,20 @@ public final class SizeConfigurationBuckets implements Parcelable {
            if (config.smallestScreenWidthDp != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
                smallest.put(config.smallestScreenWidthDp, 0);
            }
            if ((curScreenLayoutSize = config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
                    != Configuration.SCREENLAYOUT_SIZE_UNDEFINED) {
                screenLayoutSize.put(curScreenLayoutSize, 0);
            }
            if (!screenLayoutLongSet && (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK)
                    != Configuration.SCREENLAYOUT_LONG_UNDEFINED) {
                screenLayoutLongSet = true;
            }
        }
        mHorizontal = horizontal.copyKeys();
        mVertical = vertical.copyKeys();
        mSmallest = smallest.copyKeys();
        mScreenLayoutSize = screenLayoutSize.copyKeys();
        mScreenLayoutLongSet = screenLayoutLongSet;
    }

    /**
@@ -82,11 +108,20 @@ public final class SizeConfigurationBuckets implements Parcelable {
     * This is a static helper to deal with null `buckets`. When no buckets have been specified,
     * this actually filters out all 3 size-configs. This is legacy behavior.
     */
    public static int filterDiff(int diff, Configuration oldConfig, Configuration newConfig,
            @Nullable SizeConfigurationBuckets buckets) {
    public static int filterDiff(int diff, @NonNull Configuration oldConfig,
            @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) {
        final boolean nonSizeLayoutFieldsUnchanged =
                areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout);
        if (buckets == null) {
            // Only unflip CONFIG_SCREEN_LAYOUT if non-size-related  attributes of screen layout do
            // not change.
            if (nonSizeLayoutFieldsUnchanged) {
                return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
                        | CONFIG_SCREEN_LAYOUT);
            } else {
                return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE);
            }
        }
        if ((diff & CONFIG_SCREEN_SIZE) != 0) {
            final boolean crosses = buckets.crossesHorizontalSizeThreshold(oldConfig.screenWidthDp,
                    newConfig.screenWidthDp)
@@ -103,6 +138,13 @@ public final class SizeConfigurationBuckets implements Parcelable {
                diff &= ~CONFIG_SMALLEST_SCREEN_SIZE;
            }
        }
        if ((diff & CONFIG_SCREEN_LAYOUT) != 0 && nonSizeLayoutFieldsUnchanged) {
            if (!buckets.crossesScreenLayoutSizeThreshold(oldConfig, newConfig)
                    && !buckets.crossesScreenLayoutLongThreshold(oldConfig.screenLayout,
                    newConfig.screenLayout)) {
                diff &= ~CONFIG_SCREEN_LAYOUT;
            }
        }
        return diff;
    }

@@ -118,6 +160,61 @@ public final class SizeConfigurationBuckets implements Parcelable {
        return crossesSizeThreshold(mSmallest, firstDp, secondDp);
    }

    /**
     * Returns whether a screen layout size threshold has been crossed.
     */
    @VisibleForTesting
    public boolean crossesScreenLayoutSizeThreshold(@NonNull Configuration firstConfig,
            @NonNull Configuration secondConfig) {
        // If both the old and new screen layout are equal (both can be undefined), then no
        // threshold is crossed.
        if ((firstConfig.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
                == (secondConfig.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)) {
            return false;
        }
        // Any time the new layout size is smaller than the old layout size, the activity has
        // crossed a size threshold because layout size represents the smallest possible size the
        // activity can occupy.
        if (!secondConfig.isLayoutSizeAtLeast(firstConfig.screenLayout
                & Configuration.SCREENLAYOUT_SIZE_MASK)) {
            return true;
        }
        // If the new layout size is at least as large as the old layout size, then check if the new
        // layout size has crossed a threshold.
        if (mScreenLayoutSize != null) {
            for (int screenLayoutSize : mScreenLayoutSize) {
                if (firstConfig.isLayoutSizeAtLeast(screenLayoutSize)
                        != secondConfig.isLayoutSizeAtLeast(screenLayoutSize)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean crossesScreenLayoutLongThreshold(int firstScreenLayout,
            int secondScreenLayout) {
        final int firstScreenLayoutLongValue = firstScreenLayout
                & Configuration.SCREENLAYOUT_LONG_MASK;
        final int secondScreenLayoutLongValue = secondScreenLayout
                & Configuration.SCREENLAYOUT_LONG_MASK;
        return mScreenLayoutLongSet && firstScreenLayoutLongValue != secondScreenLayoutLongValue;
    }

    /**
     * Returns whether non-size related screen layout attributes have changed. If true, then
     * {@link ActivityInfo#CONFIG_SCREEN_LAYOUT} should not be filtered out in
     * {@link SizeConfigurationBuckets#filterDiff()} because the non-size related attributes
     * do not have a bucket range like the size-related attributes of screen layout.
     */
    @VisibleForTesting
    public static boolean areNonSizeLayoutFieldsUnchanged(int oldScreenLayout,
            int newScreenLayout) {
        final int nonSizeRelatedFields = Configuration.SCREENLAYOUT_LAYOUTDIR_MASK
                | Configuration.SCREENLAYOUT_ROUND_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED;
        return (oldScreenLayout & nonSizeRelatedFields) == (newScreenLayout & nonSizeRelatedFields);
    }

    /**
     * The purpose of this method is to decide whether the activity needs to be relaunched upon
     * changing its size. In most cases the activities don't need to be relaunched, if the resize
@@ -132,7 +229,8 @@ public final class SizeConfigurationBuckets implements Parcelable {
     * it resizes width from 620dp to 700dp, it won't be relaunched as it stays on the same side
     * of the threshold.
     */
    private static boolean crossesSizeThreshold(int[] thresholds, int firstDp,
    @VisibleForTesting
    public static boolean crossesSizeThreshold(int[] thresholds, int firstDp,
            int secondDp) {
        if (thresholds == null) {
            return false;
@@ -150,12 +248,13 @@ public final class SizeConfigurationBuckets implements Parcelable {
    @Override
    public String toString() {
        return Arrays.toString(mHorizontal) + " " + Arrays.toString(mVertical) + " "
                + Arrays.toString(mSmallest);
                + Arrays.toString(mSmallest) + " " + Arrays.toString(mScreenLayoutSize) + " "
                + mScreenLayoutLongSet;
    }



    // Code below generated by codegen v1.0.22.
    // Code below generated by codegen v1.0.23.
    //
    // DO NOT MODIFY!
    // CHECKSTYLE:OFF Generated code
@@ -177,15 +276,25 @@ public final class SizeConfigurationBuckets implements Parcelable {
     *   Vertical (screenHeightDp) buckets
     * @param smallest
     *   Smallest (smallestScreenWidthDp) buckets
     * @param screenLayoutSize
     *   Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets
     * @param screenLayoutLongSet
     *   Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a
     *   value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and
     *   SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change.
     */
    @DataClass.Generated.Member
    public SizeConfigurationBuckets(
            @Nullable int[] horizontal,
            @Nullable int[] vertical,
            @Nullable int[] smallest) {
            @Nullable int[] smallest,
            @Nullable int[] screenLayoutSize,
            boolean screenLayoutLongSet) {
        this.mHorizontal = horizontal;
        this.mVertical = vertical;
        this.mSmallest = smallest;
        this.mScreenLayoutSize = screenLayoutSize;
        this.mScreenLayoutLongSet = screenLayoutLongSet;

        // onConstructed(); // You can define this method to get a callback
    }
@@ -214,6 +323,24 @@ public final class SizeConfigurationBuckets implements Parcelable {
        return mSmallest;
    }

    /**
     * Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets
     */
    @DataClass.Generated.Member
    public @Nullable int[] getScreenLayoutSize() {
        return mScreenLayoutSize;
    }

    /**
     * Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a
     * value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and
     * SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change.
     */
    @DataClass.Generated.Member
    public boolean isScreenLayoutLongSet() {
        return mScreenLayoutLongSet;
    }

    @Override
    @DataClass.Generated.Member
    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
@@ -221,13 +348,16 @@ public final class SizeConfigurationBuckets implements Parcelable {
        // void parcelFieldName(Parcel dest, int flags) { ... }

        byte flg = 0;
        if (mScreenLayoutLongSet) flg |= 0x10;
        if (mHorizontal != null) flg |= 0x1;
        if (mVertical != null) flg |= 0x2;
        if (mSmallest != null) flg |= 0x4;
        if (mScreenLayoutSize != null) flg |= 0x8;
        dest.writeByte(flg);
        if (mHorizontal != null) dest.writeIntArray(mHorizontal);
        if (mVertical != null) dest.writeIntArray(mVertical);
        if (mSmallest != null) dest.writeIntArray(mSmallest);
        if (mScreenLayoutSize != null) dest.writeIntArray(mScreenLayoutSize);
    }

    @Override
@@ -242,13 +372,17 @@ public final class SizeConfigurationBuckets implements Parcelable {
        // static FieldType unparcelFieldName(Parcel in) { ... }

        byte flg = in.readByte();
        boolean screenLayoutLongSet = (flg & 0x10) != 0;
        int[] horizontal = (flg & 0x1) == 0 ? null : in.createIntArray();
        int[] vertical = (flg & 0x2) == 0 ? null : in.createIntArray();
        int[] smallest = (flg & 0x4) == 0 ? null : in.createIntArray();
        int[] screenLayoutSize = (flg & 0x8) == 0 ? null : in.createIntArray();

        this.mHorizontal = horizontal;
        this.mVertical = vertical;
        this.mSmallest = smallest;
        this.mScreenLayoutSize = screenLayoutSize;
        this.mScreenLayoutLongSet = screenLayoutLongSet;

        // onConstructed(); // You can define this method to get a callback
    }
@@ -268,10 +402,10 @@ public final class SizeConfigurationBuckets implements Parcelable {
    };

    @DataClass.Generated(
            time = 1615845864280L,
            codegenVersion = "1.0.22",
            time = 1628273704583L,
            codegenVersion = "1.0.23",
            sourceFile = "frameworks/base/core/java/android/window/SizeConfigurationBuckets.java",
            inputSignatures = "private final @android.annotation.Nullable int[] mHorizontal\nprivate final @android.annotation.Nullable int[] mVertical\nprivate final @android.annotation.Nullable int[] mSmallest\npublic static  int filterDiff(int,android.content.res.Configuration,android.content.res.Configuration,android.window.SizeConfigurationBuckets)\nprivate  boolean crossesHorizontalSizeThreshold(int,int)\nprivate  boolean crossesVerticalSizeThreshold(int,int)\nprivate  boolean crossesSmallestSizeThreshold(int,int)\nprivate static  boolean crossesSizeThreshold(int[],int,int)\npublic @java.lang.Override java.lang.String toString()\nclass SizeConfigurationBuckets extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true)")
            inputSignatures = "private final @android.annotation.Nullable int[] mHorizontal\nprivate final @android.annotation.Nullable int[] mVertical\nprivate final @android.annotation.Nullable int[] mSmallest\nprivate final @android.annotation.Nullable int[] mScreenLayoutSize\nprivate final  boolean mScreenLayoutLongSet\npublic static  int filterDiff(int,android.content.res.Configuration,android.content.res.Configuration,android.window.SizeConfigurationBuckets)\nprivate  boolean crossesHorizontalSizeThreshold(int,int)\nprivate  boolean crossesVerticalSizeThreshold(int,int)\nprivate  boolean crossesSmallestSizeThreshold(int,int)\npublic @com.android.internal.annotations.VisibleForTesting boolean crossesScreenLayoutSizeThreshold(android.content.res.Configuration,android.content.res.Configuration)\nprivate  boolean crossesScreenLayoutLongThreshold(int,int)\npublic static @com.android.internal.annotations.VisibleForTesting boolean areNonSizeLayoutFieldsUnchanged(int,int)\npublic static @com.android.internal.annotations.VisibleForTesting boolean crossesSizeThreshold(int[],int,int)\npublic @java.lang.Override java.lang.String toString()\nclass SizeConfigurationBuckets extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true)")
    @Deprecated
    private void __metadata() {}

+4 −0
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ static struct configuration_offsets_t {
  jfieldID mSmallestScreenWidthDpOffset;
  jfieldID mScreenWidthDpOffset;
  jfieldID mScreenHeightDpOffset;
  jfieldID mScreenLayoutOffset;
} gConfigurationOffsets;

static struct arraymap_offsets_t {
@@ -1019,6 +1020,7 @@ static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config&
                   config.smallestScreenWidthDp);
  env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
  env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
  env->SetIntField(result, gConfigurationOffsets.mScreenLayoutOffset, config.screenLayout);
  return result;
}

@@ -1553,6 +1555,8 @@ int register_android_content_AssetManager(JNIEnv* env) {
      GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I");
  gConfigurationOffsets.mScreenHeightDpOffset =
      GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
  gConfigurationOffsets.mScreenLayoutOffset =
          GetFieldIDOrDie(env, configurationClass, "screenLayout", "I");

  jclass arrayMapClass = FindClassOrDie(env, "android/util/ArrayMap");
  gArrayMapOffsets.classObject = MakeGlobalRefOrDie(env, arrayMapClass);
+379 −0

File added.

Preview size limit exceeded, changes collapsed.