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