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