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