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.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