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
This commit is contained in:
Shivam Agrawal
2021-07-08 10:51:43 -04:00
parent bebfe77990
commit 8bec600008
3 changed files with 528 additions and 11 deletions

View File

@@ -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,23 +85,42 @@ 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;
}
/**
* Get the changes between two configurations but don't count changes in sizes if they don't
* cross boundaries that are important to the app.
* cross boundaries that are important to the app.
*
* 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) {
return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE);
// 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,
@@ -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() {}

View File

@@ -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);

View File

@@ -0,0 +1,379 @@
/*
* Copyright (C) 2021 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.window;
import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
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;
import static android.content.res.Configuration.SCREENLAYOUT_COMPAT_NEEDED;
import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_UNDEFINED;
import static android.content.res.Configuration.SCREENLAYOUT_LONG_NO;
import static android.content.res.Configuration.SCREENLAYOUT_LONG_YES;
import static android.content.res.Configuration.SCREENLAYOUT_ROUND_NO;
import static android.content.res.Configuration.SCREENLAYOUT_ROUND_UNDEFINED;
import static android.content.res.Configuration.SCREENLAYOUT_ROUND_YES;
import static android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE;
import static android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL;
import static android.content.res.Configuration.SCREENLAYOUT_SIZE_SMALL;
import static android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.res.Configuration;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
/**
* Tests for {@link SizeConfigurationBuckets}
*
* Build/Install/Run:
* atest FrameworksMockingCoreTests:SizeConfigurationBucketsTest
*/
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class SizeConfigurationBucketsTest {
/**
* Tests that a change in any of the non-size-related screen layout fields results in
* {@link SizeConfigurationBuckets#areNonSizeLayoutFieldsUnchanged} returning false.
*/
@Test
public void testNonSizeRelatedScreenLayoutFields() {
// Test layout direction
assertEquals(true, SizeConfigurationBuckets
.areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_LAYOUTDIR_UNDEFINED));
assertEquals(false, SizeConfigurationBuckets
.areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_LAYOUTDIR_LTR));
assertEquals(false, SizeConfigurationBuckets
.areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_LAYOUTDIR_RTL));
// Test layout roundness
assertEquals(true, SizeConfigurationBuckets
.areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_ROUND_UNDEFINED));
assertEquals(false, SizeConfigurationBuckets
.areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_ROUND_NO));
assertEquals(false, SizeConfigurationBuckets
.areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_ROUND_YES));
// Test layout compat needed
assertEquals(false, SizeConfigurationBuckets
.areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_COMPAT_NEEDED));
}
/**
* Tests that null size configuration buckets unflips the correct configuration flags.
*/
@Test
public void testNullSizeConfigurationBuckets() {
// Check that all 3 size configurations are filtered out of the diff if the buckets are null
// and non-size attributes of screen layout are unchanged. Add a non-size related config
// change (i.e. CONFIG_LOCALE) to test that the diff is not set to zero.
final int diff = CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_SCREEN_LAYOUT
| CONFIG_LOCALE;
final int filteredDiffNonSizeLayoutUnchanged = SizeConfigurationBuckets.filterDiff(diff,
Configuration.EMPTY, Configuration.EMPTY, null);
assertEquals(CONFIG_LOCALE, filteredDiffNonSizeLayoutUnchanged);
// Check that only screen size and smallest screen size are filtered out of the diff if the
// buckets are null and non-size attributes of screen layout are changed.
final Configuration newConfig = new Configuration();
newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
Configuration.EMPTY, newConfig, null);
assertEquals(CONFIG_SCREEN_LAYOUT | CONFIG_LOCALE, filteredDiffNonSizeLayoutChanged);
}
/**
* Tests that {@link SizeConfigurationBuckets.crossesSizeThreshold()} correctly checks whether
* the bucket thresholds have or have not been crossed. This test includes boundary checks
* to ensure that arithmetic is inclusive and exclusive in the right places.
*/
@Test
public void testCrossesSizeThreshold() {
final int[] thresholds = new int[] { 360, 600 };
final int nThresholds = thresholds.length;
for (int i = -1; i < nThresholds; i++) {
final int minValueInBucket = i < 0 ? 0 : thresholds[i];
final int maxValueInBucket = i < nThresholds - 1
? thresholds[i + 1] - 1 : Integer.MAX_VALUE;
final int bucketRange = maxValueInBucket - minValueInBucket;
// Set old value to 1/4 in between the two thresholds.
final int oldValue = (int) (minValueInBucket + bucketRange * 0.25);
// Test 3 values of new value spread across bucket range: minValueInBucket, bucket
// midpoint, and max value in bucket. In all 3 cases, the bucket has not changed so
// {@link SizeConfigurationBuckets#crossedSizeThreshold()} should return false.
checkCrossesSizeThreshold(thresholds, oldValue, minValueInBucket, false);
checkCrossesSizeThreshold(thresholds, oldValue,
(int) (minValueInBucket + bucketRange * 0.5), false);
checkCrossesSizeThreshold(thresholds, oldValue, maxValueInBucket, false);
// Test 4 values of size spread outside of bucket range: more than 1 less than min
// value, 1 less than min value, 1 more than max value, and more than 1 more than max
// value. In all 4 cases, the bucket has changed so
// {@link SizeConfigurationBuckets#crossedSizeThreshold()} should return true.
// Only test less than min value if min value > 0.
if (minValueInBucket > 0) {
checkCrossesSizeThreshold(thresholds, oldValue, minValueInBucket - 20, true);
checkCrossesSizeThreshold(thresholds, oldValue, minValueInBucket - 1, true);
}
// Only test greater than max value if not in highest bucket.
if (i < nThresholds - 1) {
checkCrossesSizeThreshold(thresholds, oldValue, maxValueInBucket + 1, true);
checkCrossesSizeThreshold(thresholds, oldValue, maxValueInBucket + 20, true);
}
}
}
/**
* Tests that if screen layout size changed but did not cross a threshold, the filtered diff
* does not include screen layout.
*/
@Test
public void testScreenLayoutFilteredIfSizeDidNotCrossThreshold() {
// Set only small and large sizes
final Configuration[] sizeConfigs = new Configuration[2];
sizeConfigs[0] = new Configuration();
sizeConfigs[0].screenLayout |= SCREENLAYOUT_SIZE_SMALL;
sizeConfigs[1] = new Configuration();
sizeConfigs[1].screenLayout |= SCREENLAYOUT_SIZE_LARGE;
final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(sizeConfigs);
// Change screen layout size from small to normal and check that screen layout flag is
// not part of the diff because a threshold was not crossed.
final int diff = CONFIG_SCREEN_LAYOUT;
final Configuration oldConfig = new Configuration();
oldConfig.screenLayout |= SCREENLAYOUT_SIZE_SMALL;
final Configuration newConfig = new Configuration();
newConfig.screenLayout |= SCREENLAYOUT_SIZE_NORMAL;
final int filteredDiff = SizeConfigurationBuckets.filterDiff(diff, oldConfig, newConfig,
sizeBuckets);
assertEquals(0, filteredDiff);
// If a non-size attribute of screen layout changed, then screen layout should not be
// filtered from the diff.
newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
oldConfig, newConfig, sizeBuckets);
assertEquals(CONFIG_SCREEN_LAYOUT, filteredDiffNonSizeLayoutChanged);
}
/**
* Tests that if screen layout size changed and did cross a threshold, the filtered diff
* includes screen layout.
*/
@Test
public void testScreenLayoutNotFilteredIfSizeCrossedThreshold() {
// Set only small and normal sizes
final Configuration[] sizeConfigs = new Configuration[2];
sizeConfigs[0] = new Configuration();
sizeConfigs[0].screenLayout |= SCREENLAYOUT_SIZE_SMALL;
sizeConfigs[1] = new Configuration();
sizeConfigs[1].screenLayout |= SCREENLAYOUT_SIZE_NORMAL;
final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(sizeConfigs);
// Change screen layout size from small to normal and check that screen layout flag is
// still part of the diff because a threshold was crossed.
final int diff = CONFIG_SCREEN_LAYOUT;
final Configuration oldConfig = new Configuration();
oldConfig.screenLayout |= SCREENLAYOUT_SIZE_SMALL;
final Configuration newConfig = new Configuration();
newConfig.screenLayout |= SCREENLAYOUT_SIZE_NORMAL;
final int filteredDiff = SizeConfigurationBuckets.filterDiff(diff, oldConfig, newConfig,
sizeBuckets);
assertEquals(CONFIG_SCREEN_LAYOUT, filteredDiff);
}
/**
* Tests that anytime screen layout size is decreased, the filtered diff still includes screen
* layout.
*/
@Test
public void testScreenLayoutNotFilteredIfSizeDecreased() {
// The size thresholds can be anything, but can't be null
final int[] horizontalThresholds = new int[] { 360, 600 };
final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(
horizontalThresholds, null /* vertical */, null /* smallest */,
null /* screenLayoutSize */, false /* screenLayoutLongSet */);
final int[] sizeValuesInOrder = new int[] {
SCREENLAYOUT_SIZE_SMALL, SCREENLAYOUT_SIZE_NORMAL, SCREENLAYOUT_SIZE_LARGE,
SCREENLAYOUT_SIZE_XLARGE
};
final int nSizes = sizeValuesInOrder.length;
for (int larger = nSizes - 1; larger > 0; larger--) {
for (int smaller = larger - 1; smaller >= 0; smaller--) {
final Configuration oldConfig = new Configuration();
oldConfig.screenLayout |= sizeValuesInOrder[larger];
final Configuration newConfig = new Configuration();
newConfig.screenLayout |= sizeValuesInOrder[smaller];
assertTrue(String.format("oldSize=%d, newSize=%d", oldConfig.screenLayout,
newConfig.screenLayout),
sizeBuckets.crossesScreenLayoutSizeThreshold(oldConfig, newConfig));
}
}
}
/**
* Tests that if screen layout long changed but did not cross a threshold, the filtered diff
* does not include screen layout.
*/
@Test
public void testScreenLayoutFilteredIfLongDidNotCrossThreshold() {
// Do not set any long threshold
final Configuration[] sizeConfigs = new Configuration[1];
sizeConfigs[0] = Configuration.EMPTY;
final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(sizeConfigs);
// Change screen layout long from not long to long and check that screen layout flag is
// not part of the diff because a threshold was not crossed.
final int diff = CONFIG_SCREEN_LAYOUT;
final Configuration oldConfig = new Configuration();
oldConfig.screenLayout |= SCREENLAYOUT_LONG_NO;
final Configuration newConfig = new Configuration();
newConfig.screenLayout |= SCREENLAYOUT_LONG_YES;
final int filteredDiff = SizeConfigurationBuckets.filterDiff(diff, oldConfig, newConfig,
sizeBuckets);
assertEquals(0, filteredDiff);
// If a non-size attribute of screen layout changed, then screen layout should not be
// filtered from the diff.
newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
oldConfig, newConfig, sizeBuckets);
assertEquals(CONFIG_SCREEN_LAYOUT, filteredDiffNonSizeLayoutChanged);
}
/**
* Tests that if screen layout long changed and did cross a threshold, the filtered diff
* includes screen layout.
*/
@Test
public void testScreenLayoutNotFilteredIfLongCrossedThreshold() {
// Set only small and normal sizes
final Configuration[] sizeConfigs = new Configuration[1];
sizeConfigs[0] = new Configuration();
sizeConfigs[0].screenLayout |= SCREENLAYOUT_LONG_NO;
final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(sizeConfigs);
// Change screen layout long from not long to long and check that screen layout flag is
// still part of the diff because a threshold was crossed.
final int diff = CONFIG_SCREEN_LAYOUT;
final Configuration oldConfig = new Configuration();
oldConfig.screenLayout |= SCREENLAYOUT_LONG_NO;
final Configuration newConfig = new Configuration();
newConfig.screenLayout |= SCREENLAYOUT_LONG_YES;
final int filteredDiff = SizeConfigurationBuckets.filterDiff(diff, oldConfig, newConfig,
sizeBuckets);
assertEquals(CONFIG_SCREEN_LAYOUT, filteredDiff);
}
/**
* Tests that horizontal buckets are correctly checked in
* {@link SizeConfigurationBuckets#filterDiff()}.
*/
@Test
public void testHorizontalSizeThresholds() {
final int[] horizontalThresholds = new int[] { 360, 600 };
final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(
horizontalThresholds, null /* vertical */, null /* smallest */,
null /* screenLayoutSize */, false /* screenLayoutLongSet */);
final Configuration oldConfig = new Configuration();
final Configuration newConfig = new Configuration();
oldConfig.screenWidthDp = 480;
// Test that value within bucket filters out screen size config
newConfig.screenWidthDp = 520;
assertEquals(0, SizeConfigurationBuckets.filterDiff(CONFIG_SCREEN_SIZE, oldConfig,
newConfig, sizeBuckets));
// Test that value outside bucket does not filter out screen size config
newConfig.screenWidthDp = 640;
assertEquals(CONFIG_SCREEN_SIZE, SizeConfigurationBuckets.filterDiff(CONFIG_SCREEN_SIZE,
oldConfig, newConfig, sizeBuckets));
}
/**
* Tests that vertical buckets are correctly checked in
* {@link SizeConfigurationBuckets#filterDiff()}.
*/
@Test
public void testVerticalSizeThresholds() {
final int[] verticalThresholds = new int[] { 360, 600 };
final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(
null, verticalThresholds /* vertical */, null /* smallest */,
null /* screenLayoutSize */, false /* screenLayoutLongSet */);
final Configuration oldConfig = new Configuration();
final Configuration newConfig = new Configuration();
oldConfig.screenHeightDp = 480;
// Test that value within bucket filters out screen size config
newConfig.screenHeightDp = 520;
assertEquals(0, SizeConfigurationBuckets.filterDiff(CONFIG_SCREEN_SIZE, oldConfig,
newConfig, sizeBuckets));
// Test that value outside bucket does not filter out screen size config
newConfig.screenHeightDp = 640;
assertEquals(CONFIG_SCREEN_SIZE, SizeConfigurationBuckets.filterDiff(CONFIG_SCREEN_SIZE,
oldConfig, newConfig, sizeBuckets));
}
/**
* Tests that smallest width buckets are correctly checked in
* {@link SizeConfigurationBuckets#filterDiff()}.
*/
@Test
public void testSmallestWidthSizeThresholds() {
final int[] smallestWidthThresholds = new int[] { 360, 600 };
final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(
null, null /* vertical */, smallestWidthThresholds /* smallest */,
null /* screenLayoutSize */, false /* screenLayoutLongSet */);
final Configuration oldConfig = new Configuration();
final Configuration newConfig = new Configuration();
oldConfig.smallestScreenWidthDp = 480;
// Test that value within bucket filters out smallest screen size config
newConfig.smallestScreenWidthDp = 520;
assertEquals(0, SizeConfigurationBuckets.filterDiff(CONFIG_SMALLEST_SCREEN_SIZE, oldConfig,
newConfig, sizeBuckets));
// Test that value outside bucket does not filter out smallest screen size config
newConfig.smallestScreenWidthDp = 640;
assertEquals(CONFIG_SMALLEST_SCREEN_SIZE, SizeConfigurationBuckets.filterDiff(
CONFIG_SMALLEST_SCREEN_SIZE, oldConfig, newConfig, sizeBuckets));
}
private void checkCrossesSizeThreshold(int[] thresholds, int oldValue, int newValue,
boolean expected) {
final String errorString = String.format(
"thresholds=%s, oldValue=%d, newValue=%d, expected=%b", Arrays.toString(thresholds),
oldValue, newValue, expected);
final boolean actual = SizeConfigurationBuckets.crossesSizeThreshold(thresholds, oldValue,
newValue);
assertEquals(errorString, expected, actual);
}
}