/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import android.content.ComponentCallbacks;
import android.content.res.Configuration;
import java.text.BreakIterator;
import java.util.Locale;
/**
* This class contains the implementation of text segment iterators
* for accessibility support.
*
* Note: Such iterators are needed in the view package since we want
* to be able to iterator over content description of any view.
*
* @hide
*/
public final class AccessibilityIterators {
/**
* @hide
*/
public static interface TextSegmentIterator {
public int[] following(int current);
public int[] preceding(int current);
}
/**
* @hide
*/
public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator {
protected String mText;
private final int[] mSegment = new int[2];
public void initialize(String text) {
mText = text;
}
protected int[] getRange(int start, int end) {
if (start < 0 || end < 0 || start == end) {
return null;
}
mSegment[0] = start;
mSegment[1] = end;
return mSegment;
}
}
static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator
implements ComponentCallbacks {
private static CharacterTextSegmentIterator sInstance;
private Locale mLocale;
protected BreakIterator mImpl;
public static CharacterTextSegmentIterator getInstance(Locale locale) {
if (sInstance == null) {
sInstance = new CharacterTextSegmentIterator(locale);
}
return sInstance;
}
private CharacterTextSegmentIterator(Locale locale) {
mLocale = locale;
onLocaleChanged(locale);
ViewRootImpl.addConfigCallback(this);
}
@Override
public void initialize(String text) {
super.initialize(text);
mImpl.setText(text);
}
@Override
public int[] following(int offset) {
final int textLegth = mText.length();
if (textLegth <= 0) {
return null;
}
if (offset >= textLegth) {
return null;
}
int start = offset;
if (start < 0) {
start = 0;
}
while (!mImpl.isBoundary(start)) {
start = mImpl.following(start);
if (start == BreakIterator.DONE) {
return null;
}
}
final int end = mImpl.following(start);
if (end == BreakIterator.DONE) {
return null;
}
return getRange(start, end);
}
@Override
public int[] preceding(int offset) {
final int textLegth = mText.length();
if (textLegth <= 0) {
return null;
}
if (offset <= 0) {
return null;
}
int end = offset;
if (end > textLegth) {
end = textLegth;
}
while (!mImpl.isBoundary(end)) {
end = mImpl.preceding(end);
if (end == BreakIterator.DONE) {
return null;
}
}
final int start = mImpl.preceding(end);
if (start == BreakIterator.DONE) {
return null;
}
return getRange(start, end);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
Locale locale = newConfig.locale;
if (!mLocale.equals(locale)) {
mLocale = locale;
onLocaleChanged(locale);
}
}
@Override
public void onLowMemory() {
/* ignore */
}
protected void onLocaleChanged(Locale locale) {
mImpl = BreakIterator.getCharacterInstance(locale);
}
}
static class WordTextSegmentIterator extends CharacterTextSegmentIterator {
private static WordTextSegmentIterator sInstance;
public static WordTextSegmentIterator getInstance(Locale locale) {
if (sInstance == null) {
sInstance = new WordTextSegmentIterator(locale);
}
return sInstance;
}
private WordTextSegmentIterator(Locale locale) {
super(locale);
}
@Override
protected void onLocaleChanged(Locale locale) {
mImpl = BreakIterator.getWordInstance(locale);
}
@Override
public int[] following(int offset) {
final int textLegth = mText.length();
if (textLegth <= 0) {
return null;
}
if (offset >= mText.length()) {
return null;
}
int start = offset;
if (start < 0) {
start = 0;
}
while (!isLetterOrDigit(start) && !isStartBoundary(start)) {
start = mImpl.following(start);
if (start == BreakIterator.DONE) {
return null;
}
}
final int end = mImpl.following(start);
if (end == BreakIterator.DONE || !isEndBoundary(end)) {
return null;
}
return getRange(start, end);
}
@Override
public int[] preceding(int offset) {
final int textLegth = mText.length();
if (textLegth <= 0) {
return null;
}
if (offset <= 0) {
return null;
}
int end = offset;
if (end > textLegth) {
end = textLegth;
}
while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) {
end = mImpl.preceding(end);
if (end == BreakIterator.DONE) {
return null;
}
}
final int start = mImpl.preceding(end);
if (start == BreakIterator.DONE || !isStartBoundary(start)) {
return null;
}
return getRange(start, end);
}
private boolean isStartBoundary(int index) {
return isLetterOrDigit(index)
&& (index == 0 || !isLetterOrDigit(index - 1));
}
private boolean isEndBoundary(int index) {
return (index > 0 && isLetterOrDigit(index - 1))
&& (index == mText.length() || !isLetterOrDigit(index));
}
private boolean isLetterOrDigit(int index) {
if (index >= 0 && index < mText.length()) {
final int codePoint = mText.codePointAt(index);
return Character.isLetterOrDigit(codePoint);
}
return false;
}
}
static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator {
private static ParagraphTextSegmentIterator sInstance;
public static ParagraphTextSegmentIterator getInstance() {
if (sInstance == null) {
sInstance = new ParagraphTextSegmentIterator();
}
return sInstance;
}
@Override
public int[] following(int offset) {
final int textLength = mText.length();
if (textLength <= 0) {
return null;
}
if (offset >= textLength) {
return null;
}
int start = offset;
if (start < 0) {
start = 0;
}
while (start < textLength && mText.charAt(start) == '\n'
&& !isStartBoundary(start)) {
start++;
}
if (start >= textLength) {
return null;
}
int end = start + 1;
while (end < textLength && !isEndBoundary(end)) {
end++;
}
return getRange(start, end);
}
@Override
public int[] preceding(int offset) {
final int textLength = mText.length();
if (textLength <= 0) {
return null;
}
if (offset <= 0) {
return null;
}
int end = offset;
if (end > textLength) {
end = textLength;
}
while(end > 0 && mText.charAt(end - 1) == '\n' && !isEndBoundary(end)) {
end--;
}
if (end <= 0) {
return null;
}
int start = end - 1;
while (start > 0 && !isStartBoundary(start)) {
start--;
}
return getRange(start, end);
}
private boolean isStartBoundary(int index) {
return (mText.charAt(index) != '\n'
&& (index == 0 || mText.charAt(index - 1) == '\n'));
}
private boolean isEndBoundary(int index) {
return (index > 0 && mText.charAt(index - 1) != '\n'
&& (index == mText.length() || mText.charAt(index) == '\n'));
}
}
}