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