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