Home | History | Annotate | Download | only in method
      1 /*
      2  * Copyright (C) 2006 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.text.method;
     18 
     19 import android.text.InputType;
     20 import android.text.Spanned;
     21 import android.text.SpannableStringBuilder;
     22 import android.view.KeyEvent;
     23 
     24 
     25 /**
     26  * For digits-only text entry
     27  * <p></p>
     28  * As for all implementations of {@link KeyListener}, this class is only concerned
     29  * with hardware keyboards.  Software input methods have no obligation to trigger
     30  * the methods in this class.
     31  */
     32 public class DigitsKeyListener extends NumberKeyListener
     33 {
     34     private char[] mAccepted;
     35     private boolean mSign;
     36     private boolean mDecimal;
     37 
     38     private static final int SIGN = 1;
     39     private static final int DECIMAL = 2;
     40 
     41     @Override
     42     protected char[] getAcceptedChars() {
     43         return mAccepted;
     44     }
     45 
     46     /**
     47      * The characters that are used.
     48      *
     49      * @see KeyEvent#getMatch
     50      * @see #getAcceptedChars
     51      */
     52     private static final char[][] CHARACTERS = {
     53         { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
     54         { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' },
     55         { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
     56         { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' },
     57     };
     58 
     59     private static boolean isSignChar(final char c) {
     60         return c == '-' || c == '+';
     61     }
     62 
     63     // TODO: Needs internationalization
     64     private static boolean isDecimalPointChar(final char c) {
     65         return c == '.';
     66     }
     67 
     68     /**
     69      * Allocates a DigitsKeyListener that accepts the digits 0 through 9.
     70      */
     71     public DigitsKeyListener() {
     72         this(false, false);
     73     }
     74 
     75     /**
     76      * Allocates a DigitsKeyListener that accepts the digits 0 through 9,
     77      * plus the minus sign (only at the beginning) and/or decimal point
     78      * (only one per field) if specified.
     79      */
     80     public DigitsKeyListener(boolean sign, boolean decimal) {
     81         mSign = sign;
     82         mDecimal = decimal;
     83 
     84         int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
     85         mAccepted = CHARACTERS[kind];
     86     }
     87 
     88     /**
     89      * Returns a DigitsKeyListener that accepts the digits 0 through 9.
     90      */
     91     public static DigitsKeyListener getInstance() {
     92         return getInstance(false, false);
     93     }
     94 
     95     /**
     96      * Returns a DigitsKeyListener that accepts the digits 0 through 9,
     97      * plus the minus sign (only at the beginning) and/or decimal point
     98      * (only one per field) if specified.
     99      */
    100     public static DigitsKeyListener getInstance(boolean sign, boolean decimal) {
    101         int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
    102 
    103         if (sInstance[kind] != null)
    104             return sInstance[kind];
    105 
    106         sInstance[kind] = new DigitsKeyListener(sign, decimal);
    107         return sInstance[kind];
    108     }
    109 
    110     /**
    111      * Returns a DigitsKeyListener that accepts only the characters
    112      * that appear in the specified String.  Note that not all characters
    113      * may be available on every keyboard.
    114      */
    115     public static DigitsKeyListener getInstance(String accepted) {
    116         // TODO: do we need a cache of these to avoid allocating?
    117 
    118         DigitsKeyListener dim = new DigitsKeyListener();
    119 
    120         dim.mAccepted = new char[accepted.length()];
    121         accepted.getChars(0, accepted.length(), dim.mAccepted, 0);
    122 
    123         return dim;
    124     }
    125 
    126     public int getInputType() {
    127         int contentType = InputType.TYPE_CLASS_NUMBER;
    128         if (mSign) {
    129             contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
    130         }
    131         if (mDecimal) {
    132             contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
    133         }
    134         return contentType;
    135     }
    136 
    137     @Override
    138     public CharSequence filter(CharSequence source, int start, int end,
    139                                Spanned dest, int dstart, int dend) {
    140         CharSequence out = super.filter(source, start, end, dest, dstart, dend);
    141 
    142         if (mSign == false && mDecimal == false) {
    143             return out;
    144         }
    145 
    146         if (out != null) {
    147             source = out;
    148             start = 0;
    149             end = out.length();
    150         }
    151 
    152         int sign = -1;
    153         int decimal = -1;
    154         int dlen = dest.length();
    155 
    156         /*
    157          * Find out if the existing text has a sign or decimal point characters.
    158          */
    159 
    160         for (int i = 0; i < dstart; i++) {
    161             char c = dest.charAt(i);
    162 
    163             if (isSignChar(c)) {
    164                 sign = i;
    165             } else if (isDecimalPointChar(c)) {
    166                 decimal = i;
    167             }
    168         }
    169         for (int i = dend; i < dlen; i++) {
    170             char c = dest.charAt(i);
    171 
    172             if (isSignChar(c)) {
    173                 return "";    // Nothing can be inserted in front of a sign character.
    174             } else if (isDecimalPointChar(c)) {
    175                 decimal = i;
    176             }
    177         }
    178 
    179         /*
    180          * If it does, we must strip them out from the source.
    181          * In addition, a sign character must be the very first character,
    182          * and nothing can be inserted before an existing sign character.
    183          * Go in reverse order so the offsets are stable.
    184          */
    185 
    186         SpannableStringBuilder stripped = null;
    187 
    188         for (int i = end - 1; i >= start; i--) {
    189             char c = source.charAt(i);
    190             boolean strip = false;
    191 
    192             if (isSignChar(c)) {
    193                 if (i != start || dstart != 0) {
    194                     strip = true;
    195                 } else if (sign >= 0) {
    196                     strip = true;
    197                 } else {
    198                     sign = i;
    199                 }
    200             } else if (isDecimalPointChar(c)) {
    201                 if (decimal >= 0) {
    202                     strip = true;
    203                 } else {
    204                     decimal = i;
    205                 }
    206             }
    207 
    208             if (strip) {
    209                 if (end == start + 1) {
    210                     return "";  // Only one character, and it was stripped.
    211                 }
    212 
    213                 if (stripped == null) {
    214                     stripped = new SpannableStringBuilder(source, start, end);
    215                 }
    216 
    217                 stripped.delete(i - start, i + 1 - start);
    218             }
    219         }
    220 
    221         if (stripped != null) {
    222             return stripped;
    223         } else if (out != null) {
    224             return out;
    225         } else {
    226             return null;
    227         }
    228     }
    229 
    230     private static DigitsKeyListener[] sInstance = new DigitsKeyListener[4];
    231 }
    232