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