1 /* 2 * Copyright (C) 2012 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.view; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.content.res.Configuration; 21 22 import java.text.BreakIterator; 23 import java.util.Locale; 24 25 /** 26 * This class contains the implementation of text segment iterators 27 * for accessibility support. 28 * 29 * Note: Such iterators are needed in the view package since we want 30 * to be able to iterator over content description of any view. 31 * 32 * @hide 33 */ 34 public final class AccessibilityIterators { 35 36 /** 37 * @hide 38 */ 39 public static interface TextSegmentIterator { following(int current)40 public int[] following(int current); preceding(int current)41 public int[] preceding(int current); 42 } 43 44 /** 45 * @hide 46 */ 47 public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator { 48 49 @UnsupportedAppUsage AbstractTextSegmentIterator()50 public AbstractTextSegmentIterator() { 51 } 52 53 @UnsupportedAppUsage 54 protected String mText; 55 56 private final int[] mSegment = new int[2]; 57 initialize(String text)58 public void initialize(String text) { 59 mText = text; 60 } 61 getRange(int start, int end)62 protected int[] getRange(int start, int end) { 63 if (start < 0 || end < 0 || start == end) { 64 return null; 65 } 66 mSegment[0] = start; 67 mSegment[1] = end; 68 return mSegment; 69 } 70 } 71 72 static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator 73 implements ViewRootImpl.ConfigChangedCallback { 74 private static CharacterTextSegmentIterator sInstance; 75 76 private Locale mLocale; 77 78 protected BreakIterator mImpl; 79 getInstance(Locale locale)80 public static CharacterTextSegmentIterator getInstance(Locale locale) { 81 if (sInstance == null) { 82 sInstance = new CharacterTextSegmentIterator(locale); 83 } 84 return sInstance; 85 } 86 CharacterTextSegmentIterator(Locale locale)87 private CharacterTextSegmentIterator(Locale locale) { 88 mLocale = locale; 89 onLocaleChanged(locale); 90 ViewRootImpl.addConfigCallback(this); 91 } 92 93 @Override initialize(String text)94 public void initialize(String text) { 95 super.initialize(text); 96 mImpl.setText(text); 97 } 98 99 @Override following(int offset)100 public int[] following(int offset) { 101 final int textLegth = mText.length(); 102 if (textLegth <= 0) { 103 return null; 104 } 105 if (offset >= textLegth) { 106 return null; 107 } 108 int start = offset; 109 if (start < 0) { 110 start = 0; 111 } 112 while (!mImpl.isBoundary(start)) { 113 start = mImpl.following(start); 114 if (start == BreakIterator.DONE) { 115 return null; 116 } 117 } 118 final int end = mImpl.following(start); 119 if (end == BreakIterator.DONE) { 120 return null; 121 } 122 return getRange(start, end); 123 } 124 125 @Override preceding(int offset)126 public int[] preceding(int offset) { 127 final int textLegth = mText.length(); 128 if (textLegth <= 0) { 129 return null; 130 } 131 if (offset <= 0) { 132 return null; 133 } 134 int end = offset; 135 if (end > textLegth) { 136 end = textLegth; 137 } 138 while (!mImpl.isBoundary(end)) { 139 end = mImpl.preceding(end); 140 if (end == BreakIterator.DONE) { 141 return null; 142 } 143 } 144 final int start = mImpl.preceding(end); 145 if (start == BreakIterator.DONE) { 146 return null; 147 } 148 return getRange(start, end); 149 } 150 151 @Override onConfigurationChanged(Configuration globalConfig)152 public void onConfigurationChanged(Configuration globalConfig) { 153 final Locale locale = globalConfig.getLocales().get(0); 154 if (locale == null) { 155 return; 156 } 157 if (!mLocale.equals(locale)) { 158 mLocale = locale; 159 onLocaleChanged(locale); 160 } 161 } 162 onLocaleChanged(Locale locale)163 protected void onLocaleChanged(Locale locale) { 164 mImpl = BreakIterator.getCharacterInstance(locale); 165 } 166 } 167 168 static class WordTextSegmentIterator extends CharacterTextSegmentIterator { 169 private static WordTextSegmentIterator sInstance; 170 getInstance(Locale locale)171 public static WordTextSegmentIterator getInstance(Locale locale) { 172 if (sInstance == null) { 173 sInstance = new WordTextSegmentIterator(locale); 174 } 175 return sInstance; 176 } 177 WordTextSegmentIterator(Locale locale)178 private WordTextSegmentIterator(Locale locale) { 179 super(locale); 180 } 181 182 @Override onLocaleChanged(Locale locale)183 protected void onLocaleChanged(Locale locale) { 184 mImpl = BreakIterator.getWordInstance(locale); 185 } 186 187 @Override following(int offset)188 public int[] following(int offset) { 189 final int textLegth = mText.length(); 190 if (textLegth <= 0) { 191 return null; 192 } 193 if (offset >= mText.length()) { 194 return null; 195 } 196 int start = offset; 197 if (start < 0) { 198 start = 0; 199 } 200 while (!isLetterOrDigit(start) && !isStartBoundary(start)) { 201 start = mImpl.following(start); 202 if (start == BreakIterator.DONE) { 203 return null; 204 } 205 } 206 final int end = mImpl.following(start); 207 if (end == BreakIterator.DONE || !isEndBoundary(end)) { 208 return null; 209 } 210 return getRange(start, end); 211 } 212 213 @Override preceding(int offset)214 public int[] preceding(int offset) { 215 final int textLegth = mText.length(); 216 if (textLegth <= 0) { 217 return null; 218 } 219 if (offset <= 0) { 220 return null; 221 } 222 int end = offset; 223 if (end > textLegth) { 224 end = textLegth; 225 } 226 while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) { 227 end = mImpl.preceding(end); 228 if (end == BreakIterator.DONE) { 229 return null; 230 } 231 } 232 final int start = mImpl.preceding(end); 233 if (start == BreakIterator.DONE || !isStartBoundary(start)) { 234 return null; 235 } 236 return getRange(start, end); 237 } 238 isStartBoundary(int index)239 private boolean isStartBoundary(int index) { 240 return isLetterOrDigit(index) 241 && (index == 0 || !isLetterOrDigit(index - 1)); 242 } 243 isEndBoundary(int index)244 private boolean isEndBoundary(int index) { 245 return (index > 0 && isLetterOrDigit(index - 1)) 246 && (index == mText.length() || !isLetterOrDigit(index)); 247 } 248 isLetterOrDigit(int index)249 private boolean isLetterOrDigit(int index) { 250 if (index >= 0 && index < mText.length()) { 251 final int codePoint = mText.codePointAt(index); 252 return Character.isLetterOrDigit(codePoint); 253 } 254 return false; 255 } 256 } 257 258 static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator { 259 private static ParagraphTextSegmentIterator sInstance; 260 getInstance()261 public static ParagraphTextSegmentIterator getInstance() { 262 if (sInstance == null) { 263 sInstance = new ParagraphTextSegmentIterator(); 264 } 265 return sInstance; 266 } 267 268 @Override following(int offset)269 public int[] following(int offset) { 270 final int textLength = mText.length(); 271 if (textLength <= 0) { 272 return null; 273 } 274 if (offset >= textLength) { 275 return null; 276 } 277 int start = offset; 278 if (start < 0) { 279 start = 0; 280 } 281 while (start < textLength && mText.charAt(start) == '\n' 282 && !isStartBoundary(start)) { 283 start++; 284 } 285 if (start >= textLength) { 286 return null; 287 } 288 int end = start + 1; 289 while (end < textLength && !isEndBoundary(end)) { 290 end++; 291 } 292 return getRange(start, end); 293 } 294 295 @Override preceding(int offset)296 public int[] preceding(int offset) { 297 final int textLength = mText.length(); 298 if (textLength <= 0) { 299 return null; 300 } 301 if (offset <= 0) { 302 return null; 303 } 304 int end = offset; 305 if (end > textLength) { 306 end = textLength; 307 } 308 while(end > 0 && mText.charAt(end - 1) == '\n' && !isEndBoundary(end)) { 309 end--; 310 } 311 if (end <= 0) { 312 return null; 313 } 314 int start = end - 1; 315 while (start > 0 && !isStartBoundary(start)) { 316 start--; 317 } 318 return getRange(start, end); 319 } 320 isStartBoundary(int index)321 private boolean isStartBoundary(int index) { 322 return (mText.charAt(index) != '\n' 323 && (index == 0 || mText.charAt(index - 1) == '\n')); 324 } 325 isEndBoundary(int index)326 private boolean isEndBoundary(int index) { 327 return (index > 0 && mText.charAt(index - 1) != '\n' 328 && (index == mText.length() || mText.charAt(index) == '\n')); 329 } 330 } 331 } 332