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