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