1 /* 2 * Copyright (C) 2016 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.text.style.cts; 18 19 import static org.junit.Assert.assertArrayEquals; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertFalse; 22 import static org.junit.Assert.assertTrue; 23 24 import android.content.Context; 25 import android.content.res.Configuration; 26 import android.graphics.Color; 27 import android.os.LocaleList; 28 import android.os.Parcel; 29 import android.text.SpannableString; 30 import android.text.Spanned; 31 import android.text.StaticLayout; 32 import android.text.TextPaint; 33 import android.text.style.SuggestionSpan; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 import androidx.test.ext.junit.runners.AndroidJUnit4; 38 import androidx.test.filters.SmallTest; 39 import androidx.test.platform.app.InstrumentationRegistry; 40 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import java.util.Locale; 45 46 /** 47 * Test {@link SuggestionSpan}. 48 */ 49 @SmallTest 50 @RunWith(AndroidJUnit4.class) 51 public class SuggestionSpanTest { 52 @Test testConstructorWithContext()53 public void testConstructorWithContext() { 54 final String[] suggestions = new String[] {"suggestion1", "suggestion2"}; 55 final Configuration overrideConfig = new Configuration(); 56 final Locale locale = Locale.forLanguageTag("az-Arab"); 57 overrideConfig.setLocales(new LocaleList(locale)); 58 final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext() 59 .createConfigurationContext(overrideConfig); 60 61 final SuggestionSpan span = new SuggestionSpan(context, suggestions, 62 SuggestionSpan.FLAG_AUTO_CORRECTION); 63 64 assertEquals(locale, span.getLocaleObject()); 65 assertArrayEquals(suggestions, span.getSuggestions()); 66 assertEquals(SuggestionSpan.FLAG_AUTO_CORRECTION, span.getFlags()); 67 } 68 69 @Test testGetSuggestionSpans()70 public void testGetSuggestionSpans() { 71 final String[] suggestions = new String[]{"suggestion1", "suggestion2"}; 72 final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions, 73 SuggestionSpan.FLAG_AUTO_CORRECTION); 74 assertArrayEquals("Should return the correct suggestions array", 75 suggestions, span.getSuggestions()); 76 77 final SuggestionSpan clonedSpan = cloneViaParcel(span); 78 assertArrayEquals("Should (de)serialize suggestions", 79 suggestions, clonedSpan.getSuggestions()); 80 } 81 82 @Test testGetSuggestionSpans_emptySuggestions()83 public void testGetSuggestionSpans_emptySuggestions() { 84 final String[] suggestions = new String[0]; 85 final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions, 86 SuggestionSpan.FLAG_AUTO_CORRECTION); 87 assertArrayEquals("Span should return empty suggestion array", 88 suggestions, span.getSuggestions()); 89 90 // also test parceling 91 final SuggestionSpan clonedSpan = cloneViaParcel(span); 92 assertArrayEquals("Should (de)serialize empty suggestions array", 93 suggestions, clonedSpan.getSuggestions()); 94 } 95 96 @Test testGetSuggestionSpans_suggestionsWithNullValue()97 public void testGetSuggestionSpans_suggestionsWithNullValue() { 98 final String[] suggestions = new String[]{"suggestion", null}; 99 final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions, 100 SuggestionSpan.FLAG_AUTO_CORRECTION); 101 assertArrayEquals("Should accept and return null suggestions", 102 suggestions, span.getSuggestions()); 103 104 final SuggestionSpan clonedSpan = cloneViaParcel(span); 105 assertArrayEquals("Should (de)serialize null in suggestions array", 106 suggestions, clonedSpan.getSuggestions()); 107 } 108 109 @Test testGetFlags()110 public void testGetFlags() { 111 final String[] anySuggestions = new String[0]; 112 final int flag = SuggestionSpan.FLAG_AUTO_CORRECTION; 113 SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), anySuggestions, flag); 114 115 assertEquals("Should return the flag passed in constructor", 116 flag, span.getFlags()); 117 118 final SuggestionSpan clonedSpan = cloneViaParcel(span); 119 assertEquals("Should (de)serialize flags", flag, clonedSpan.getFlags()); 120 } 121 122 @Test testEquals_returnsTrueForDeserializedInstances()123 public void testEquals_returnsTrueForDeserializedInstances() { 124 final SuggestionSpan span1 = new SuggestionSpan(null, Locale.forLanguageTag("en"), 125 new String[0], SuggestionSpan.FLAG_AUTO_CORRECTION, SuggestionSpan.class); 126 final SuggestionSpan span2 = cloneViaParcel(span1); 127 128 assertTrue("(De)serialized instances should be equal", span1.equals(span2)); 129 } 130 131 @Test testEquals_returnsTrueIfTheFlagsAreDifferent()132 public void testEquals_returnsTrueIfTheFlagsAreDifferent() { 133 final SuggestionSpan span1 = new SuggestionSpan(null, Locale.forLanguageTag("en"), 134 new String[0], SuggestionSpan.FLAG_AUTO_CORRECTION, SuggestionSpan.class); 135 final SuggestionSpan span2 = cloneViaParcel(span1); 136 span2.setFlags(SuggestionSpan.FLAG_EASY_CORRECT); 137 138 assertEquals("Should return the flag passed in set function", 139 SuggestionSpan.FLAG_EASY_CORRECT, span2.getFlags()); 140 141 assertTrue("Instances with different flags should be equal", span1.equals(span2)); 142 } 143 144 @Test testEquals_returnsFalseIfCreationTimeIsNotSame()145 public void testEquals_returnsFalseIfCreationTimeIsNotSame() { 146 final Locale anyLocale = Locale.forLanguageTag("en"); 147 final String[] anySuggestions = new String[0]; 148 final int anyFlags = SuggestionSpan.FLAG_AUTO_CORRECTION; 149 final Class anyClass = SuggestionSpan.class; 150 151 final SuggestionSpan span1 = new SuggestionSpan(null, anyLocale, anySuggestions, anyFlags, 152 anyClass); 153 try { 154 // let some time pass before constructing the other span 155 Thread.sleep(2); 156 } catch (InterruptedException e) { 157 // ignore 158 } 159 final SuggestionSpan span2 = new SuggestionSpan(null, anyLocale, anySuggestions, anyFlags, 160 anyClass); 161 162 assertFalse("Instances created at different time should not be equal", span2.equals(span1)); 163 } 164 165 /** 166 * @param locale a {@link Locale} object. 167 * @return A well-formed BCP 47 language tag representation. 168 */ 169 @Nullable toWellFormedLocale(@ullable final Locale locale)170 private Locale toWellFormedLocale(@Nullable final Locale locale) { 171 if (locale == null) { 172 return null; 173 } 174 // Drop all the malformed data. 175 return Locale.forLanguageTag(locale.toLanguageTag()); 176 } 177 178 @NonNull getNonNullLocaleString(@ullable final Locale original)179 private String getNonNullLocaleString(@Nullable final Locale original) { 180 if (original == null) { 181 return ""; 182 } 183 return original.toString(); 184 } 185 verifyGetLocaleObject(final Locale locale)186 private void verifyGetLocaleObject(final Locale locale) { 187 final SuggestionSpan span = new SuggestionSpan(locale, new String[0], 188 SuggestionSpan.FLAG_AUTO_CORRECTION); 189 // In the context of SuggestionSpan#getLocaleObject(), we do care only about subtags that 190 // can be interpreted as LanguageTag. 191 assertEquals(toWellFormedLocale(locale), span.getLocaleObject()); 192 assertEquals(getNonNullLocaleString(locale), span.getLocale()); 193 194 final SuggestionSpan cloned = cloneViaParcel(span); 195 assertEquals(span, cloned); 196 assertEquals(toWellFormedLocale(locale), cloned.getLocaleObject()); 197 assertEquals(getNonNullLocaleString(locale), cloned.getLocale()); 198 } 199 200 @Test testGetLocaleObject()201 public void testGetLocaleObject() { 202 verifyGetLocaleObject(Locale.forLanguageTag("en")); 203 verifyGetLocaleObject(Locale.forLanguageTag("en-GB")); 204 verifyGetLocaleObject(Locale.forLanguageTag("EN-GB")); 205 verifyGetLocaleObject(Locale.forLanguageTag("en-gb")); 206 verifyGetLocaleObject(Locale.forLanguageTag("En-gB")); 207 verifyGetLocaleObject(Locale.forLanguageTag("und")); 208 verifyGetLocaleObject(Locale.forLanguageTag("de-DE-u-co-phonebk")); 209 verifyGetLocaleObject(Locale.forLanguageTag("")); 210 verifyGetLocaleObject(null); 211 verifyGetLocaleObject(new Locale(" an ", " i n v a l i d ", "data")); 212 } 213 214 // Measures the width of some potentially-spanned text, assuming it's not too wide. textWidth(CharSequence text)215 private float textWidth(CharSequence text) { 216 final TextPaint tp = new TextPaint(); 217 tp.setTextSize(100.0f); // Large enough so that the difference in kerning is visible. 218 final int largeWidth = 10000; // Enough width so the whole text fits in one line. 219 final StaticLayout layout = StaticLayout.Builder.obtain( 220 text, 0, text.length(), tp, largeWidth).build(); 221 return layout.getLineWidth(0); 222 } 223 224 @Test testDoesntAffectWidth()225 public void testDoesntAffectWidth() { 226 // Roboto kerns between "P" and "." 227 final SpannableString text = new SpannableString("P."); 228 final float origLineWidth = textWidth(text); 229 230 final String[] suggestions = new String[]{"suggestion1", "suggestion2"}; 231 final SuggestionSpan span = new SuggestionSpan(Locale.US, suggestions, 232 SuggestionSpan.FLAG_AUTO_CORRECTION); 233 // Put just the "P" in a suggestion span. 234 text.setSpan(span, 0, 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 235 final float underlinedLineWidth = textWidth(text); 236 assertEquals(origLineWidth, underlinedLineWidth, 0.0f); 237 } 238 239 @NonNull cloneViaParcel(@onNull final SuggestionSpan original)240 SuggestionSpan cloneViaParcel(@NonNull final SuggestionSpan original) { 241 Parcel parcel = null; 242 try { 243 parcel = Parcel.obtain(); 244 original.writeToParcel(parcel, 0); 245 parcel.setDataPosition(0); 246 return new SuggestionSpan(parcel); 247 } finally { 248 if (parcel != null) { 249 parcel.recycle(); 250 } 251 } 252 } 253 254 @Test testGetUnderlineColor_NoUnderline()255 public void testGetUnderlineColor_NoUnderline() { 256 final String[] suggestions = new String[0]; 257 final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions, 0); 258 assertEquals(span.getUnderlineColor(), 0); 259 } 260 261 @Test testGetUnderlineColor_EasyCorrectUnderline()262 public void testGetUnderlineColor_EasyCorrectUnderline() { 263 final String[] suggestions = new String[0]; 264 final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions, 265 SuggestionSpan.FLAG_EASY_CORRECT); 266 assertEquals(span.getUnderlineColor(), Color.BLACK); 267 } 268 269 @Test testGetUnderlineColor_MisspelledUnderline()270 public void testGetUnderlineColor_MisspelledUnderline() { 271 final String[] suggestions = new String[0]; 272 final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions, 273 SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED); 274 assertEquals(span.getUnderlineColor(), Color.BLACK); 275 } 276 277 @Test testGetUnderlineColor_AutoCorrectionUnderline()278 public void testGetUnderlineColor_AutoCorrectionUnderline() { 279 final String[] suggestions = new String[0]; 280 final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions, 281 SuggestionSpan.FLAG_AUTO_CORRECTION); 282 assertEquals(span.getUnderlineColor(), Color.BLACK); 283 } 284 } 285