1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.content.res; 18 19 import static androidx.test.espresso.Espresso.onView; 20 import static androidx.test.espresso.assertion.ViewAssertions.matches; 21 import static androidx.test.espresso.matcher.ViewMatchers.withId; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import android.app.Activity; 26 import android.compat.testing.PlatformCompatChangeRule; 27 import android.os.Bundle; 28 import android.platform.test.annotations.PlatinumTest; 29 import android.platform.test.annotations.Presubmit; 30 import android.provider.Settings; 31 import android.util.PollingCheck; 32 import android.view.View; 33 import android.widget.TextView; 34 35 import androidx.test.core.app.ActivityScenario; 36 import androidx.test.espresso.matcher.BoundedMatcher; 37 import androidx.test.ext.junit.rules.ActivityScenarioRule; 38 import androidx.test.ext.junit.runners.AndroidJUnit4; 39 import androidx.test.filters.LargeTest; 40 import androidx.test.platform.app.InstrumentationRegistry; 41 42 import com.android.compatibility.common.util.ShellIdentityUtils; 43 import com.android.frameworks.coretests.R; 44 45 import org.hamcrest.Description; 46 import org.hamcrest.Matcher; 47 import org.junit.After; 48 import org.junit.Rule; 49 import org.junit.Test; 50 import org.junit.rules.TestRule; 51 import org.junit.runner.RunWith; 52 53 import java.util.concurrent.atomic.AtomicBoolean; 54 import java.util.concurrent.atomic.AtomicReference; 55 56 /** 57 * Test for verifying non-linear font scaling behavior. 58 * Build/Install/Run: 59 * atest FrameworksCoreTests:android.content.res.FontScaleConverterActivityTest 60 */ 61 @RunWith(AndroidJUnit4.class) 62 @LargeTest 63 @Presubmit 64 public class FontScaleConverterActivityTest { 65 @Rule 66 public ActivityScenarioRule<TestActivity> rule = new ActivityScenarioRule<>(TestActivity.class); 67 @Rule 68 public TestRule compatChangeRule = new PlatformCompatChangeRule(); 69 70 @After teardown()71 public void teardown() { 72 restoreSystemFontScaleToDefault(); 73 } 74 75 @PlatinumTest(focusArea = "accessibility") 76 @Test testFontsScaleNonLinearly()77 public void testFontsScaleNonLinearly() { 78 final ActivityScenario<TestActivity> scenario = rule.getScenario(); 79 80 setSystemFontScale(2f); 81 82 var densityRef = new AtomicReference<Float>(); 83 scenario.onActivity(activity -> { 84 assertThat(activity.getResources().getConfiguration().fontScale) 85 .isWithin(0.05f) 86 .of(2f); 87 densityRef.compareAndSet(null, activity.getResources().getDisplayMetrics().density); 88 }); 89 var density = densityRef.get(); 90 assertThat(density).isNotNull(); 91 92 onView(withId(R.id.text8sp)).check(matches(withTextSizeInRange( 93 15f * density, 94 16f * density 95 ))); 96 onView(withId(R.id.text18sp)).check(matches(withTextSizeInRange( 97 20f * density, 98 36f * density 99 ))); 100 onView(withId(R.id.text35sp)).check(matches(withTextSizeInRange( 101 35 * density, 102 60 * density 103 ))); 104 } 105 106 @PlatinumTest(focusArea = "accessibility") 107 @Test testOnConfigurationChanged_doesNotCrash()108 public void testOnConfigurationChanged_doesNotCrash() { 109 final ActivityScenario<TestActivity> scenario = rule.getScenario(); 110 111 scenario.onActivity(activity -> { 112 var config = new Configuration(activity.getResources().getConfiguration()); 113 config.fontScale = 1.2f; 114 115 activity.onConfigurationChanged(config); 116 activity.finish(); 117 }); 118 } 119 120 @PlatinumTest(focusArea = "accessibility") 121 @Test testUpdateConfiguration_doesNotCrash()122 public void testUpdateConfiguration_doesNotCrash() { 123 final ActivityScenario<TestActivity> scenario = rule.getScenario(); 124 125 scenario.onActivity(activity -> { 126 activity.updateConfiguration(); 127 activity.finish(); 128 }); 129 } 130 setSystemFontScale(float fontScale)131 private void setSystemFontScale(float fontScale) { 132 ShellIdentityUtils.invokeWithShellPermissions(() -> { 133 Settings.System.putFloat( 134 InstrumentationRegistry.getInstrumentation().getContext().getContentResolver(), 135 Settings.System.FONT_SCALE, 136 fontScale 137 ); 138 }); 139 140 PollingCheck.waitFor(/* timeout= */ 7000, () -> { 141 AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false); 142 rule.getScenario().onActivity(activity -> 143 isActivityAtCorrectScale.set( 144 activity.getResources() 145 .getConfiguration() 146 .fontScale == fontScale 147 ) 148 ); 149 return isActivityAtCorrectScale.get(); 150 }); 151 } 152 restoreSystemFontScaleToDefault()153 private static void restoreSystemFontScaleToDefault() { 154 ShellIdentityUtils.invokeWithShellPermissions(() -> { 155 // TODO(b/279083734): would use Settings.System.resetToDefaults() if it existed 156 Settings.System.putString( 157 InstrumentationRegistry.getInstrumentation() 158 .getContext() 159 .getContentResolver(), 160 Settings.System.FONT_SCALE, 161 null, 162 /* overrideableByRestore= */ true); 163 }); 164 165 PollingCheck.waitFor( 166 /* timeout= */ 5000, 167 () -> InstrumentationRegistry.getInstrumentation() 168 .getContext() 169 .getResources() 170 .getConfiguration() 171 .fontScale == 1 172 ); 173 } 174 withTextSizeInRange(float sizeStartPx, float sizeEndPx)175 private Matcher<View> withTextSizeInRange(float sizeStartPx, float sizeEndPx) { 176 return new BoundedMatcher<>(TextView.class) { 177 private static final float TOLERANCE = 0.05f; 178 179 @Override 180 public void describeMismatch(Object item, Description description) { 181 super.describeMismatch(item, description); 182 description.appendText("was ").appendValue(((TextView) item).getTextSize()); 183 } 184 185 @Override 186 public void describeTo(Description description) { 187 description.appendText("withTextSize between " + sizeStartPx + " and " + sizeEndPx); 188 } 189 190 @Override 191 protected boolean matchesSafely(TextView textView) { 192 var textSize = textView.getTextSize(); 193 return sizeStartPx - TOLERANCE < textSize && textSize < sizeEndPx + TOLERANCE; 194 } 195 }; 196 } 197 198 /** Test Activity */ 199 public static class TestActivity extends Activity { 200 final Configuration mConfig = new Configuration(); 201 202 @Override onCreate(Bundle savedInstanceState)203 protected void onCreate(Bundle savedInstanceState) { 204 super.onCreate(savedInstanceState); 205 getWindow().getDecorView().setKeepScreenOn(true); 206 setShowWhenLocked(true); 207 setTurnScreenOn(true); 208 209 setContentView(R.layout.font_scale_converter_activity); 210 } 211 updateConfiguration()212 public Configuration updateConfiguration() { 213 Settings.System.getConfiguration(getContentResolver(), mConfig); 214 return mConfig; 215 } 216 } 217 } 218