content: /*
   * Copyright (C) 2006 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.text.method;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.icu.text.DecimalFormatSymbols;
  import android.text.Editable;
  import android.text.InputFilter;
  import android.text.Selection;
  import android.text.Spannable;
  import android.text.SpannableStringBuilder;
  import android.text.Spanned;
  import android.text.format.DateFormat;
  import android.view.KeyEvent;
  import android.view.View;
  
  import libcore.icu.LocaleData;
  
  import java.util.Collection;
  import java.util.Locale;
  
  /**
   * For numeric text entry
   * <p></p>
   * As for all implementations of {@link KeyListener}, this class is only concerned
   * with hardware keyboards.  Software input methods have no obligation to trigger
   * the methods in this class.
   */
  public abstract class NumberKeyListener extends BaseKeyListener
      implements InputFilter
  {
      /**
       * You can say which characters you can accept.
       */
      @NonNull
      protected abstract char[] getAcceptedChars();
  
      protected int lookup(KeyEvent event, Spannable content) {
          return event.getMatch(getAcceptedChars(), getMetaState(content, event));
      }
  
      public CharSequence filter(CharSequence source, int start, int end,
                                 Spanned dest, int dstart, int dend) {
          char[] accept = getAcceptedChars();
          boolean filter = false;
  
          int i;
          for (i = start; i < end; i++) {
              if (!ok(accept, source.charAt(i))) {
                  break;
              }
          }
  
          if (i == end) {
              // It was all OK.
              return null;
          }
  
          if (end - start == 1) {
              // It was not OK, and there is only one char, so nothing remains.
              return "";
          }
  
          SpannableStringBuilder filtered =
              new SpannableStringBuilder(source, start, end);
          i -= start;
          end -= start;
  
          int len = end - start;
          // Only count down to i because the chars before that were all OK.
          for (int j = end - 1; j >= i; j--) {
              if (!ok(accept, source.charAt(j))) {
                  filtered.delete(j, j + 1);
              }
          }
  
          return filtered;
      }
  
      protected static boolean ok(char[] accept, char c) {
          for (int i = accept.length - 1; i >= 0; i--) {
              if (accept[i] == c) {
                  return true;
              }
          }
  
          return false;
      }
  
      @Override
      public boolean onKeyDown(View view, Editable content,
                               int keyCode, KeyEvent event) {
          int selStart, selEnd;
  
          {
              int a = Selection.getSelectionStart(content);
              int b = Selection.getSelectionEnd(content);
  
              selStart = Math.min(a, b);
              selEnd = Math.max(a, b);
          }
  
          if (selStart < 0 || selEnd < 0) {
              selStart = selEnd = 0;
              Selection.setSelection(content, 0);
          }
  
          int i = event != null ? lookup(event, content) : 0;
          int repeatCount = event != null ? event.getRepeatCount() : 0;
          if (repeatCount == 0) {
              if (i != 0) {
                  if (selStart != selEnd) {
                      Selection.setSelection(content, selEnd);
                  }
  
                  content.replace(selStart, selEnd, String.valueOf((char) i));
  
                  adjustMetaAfterKeypress(content);
                  return true;
              }
          } else if (i == '0' && repeatCount == 1) {
              // Pretty hackish, it replaces the 0 with the +
  
              if (selStart == selEnd && selEnd > 0 &&
                      content.charAt(selStart - 1) == '0') {
                  content.replace(selStart - 1, selEnd, String.valueOf('+'));
                  adjustMetaAfterKeypress(content);
                  return true;
              }
          }
  
          adjustMetaAfterKeypress(content);
          return super.onKeyDown(view, content, keyCode, event);
      }
  
      /* package */
      @Nullable
      static boolean addDigits(@NonNull Collection<Character> collection, @Nullable Locale locale) {
          if (locale == null) {
              return false;
          }
          final String[] digits = DecimalFormatSymbols.getInstance(locale).getDigitStrings();
          for (int i = 0; i < 10; i++) {
              if (digits[i].length() > 1) { // multi-codeunit digits. Not supported.
                  return false;
              }
              collection.add(Character.valueOf(digits[i].charAt(0)));
          }
          return true;
      }
  
      // From http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
      private static final String DATE_TIME_FORMAT_SYMBOLS =
              "GyYuUrQqMLlwWdDFgEecabBhHKkjJCmsSAzZOvVXx";
      private static final char SINGLE_QUOTE = '\'';
  
      /* package */
      static boolean addFormatCharsFromSkeleton(
              @NonNull Collection<Character> collection, @Nullable Locale locale,
              @NonNull String skeleton, @NonNull String symbolsToIgnore) {
          if (locale == null) {
              return false;
          }
          final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
          boolean outsideQuotes = true;
          for (int i = 0; i < pattern.length(); i++) {
              final char ch = pattern.charAt(i);
              if (Character.isSurrogate(ch)) { // characters outside BMP are not supported.
                  return false;
              } else if (ch == SINGLE_QUOTE) {
                  outsideQuotes = !outsideQuotes;
                  // Single quote characters should be considered if and only if they follow
                  // another single quote.
                  if (i == 0 || pattern.charAt(i - 1) != SINGLE_QUOTE) {
                      continue;
                  }
              }
  
              if (outsideQuotes) {
                  if (symbolsToIgnore.indexOf(ch) != -1) {
                      // Skip expected pattern characters.
                      continue;
                  } else if (DATE_TIME_FORMAT_SYMBOLS.indexOf(ch) != -1) {
                      // An unexpected symbols is seen. We've failed.
                      return false;
                  }
              }
              // If we are here, we are either inside quotes, or we have seen a non-pattern
              // character outside quotes. So ch is a valid character in a date.
              collection.add(Character.valueOf(ch));
          }
          return true;
      }
  
      /* package */
      static boolean addFormatCharsFromSkeletons(
              @NonNull Collection<Character> collection, @Nullable Locale locale,
              @NonNull String[] skeletons, @NonNull String symbolsToIgnore) {
          for (int i = 0; i < skeletons.length; i++) {
              final boolean success = addFormatCharsFromSkeleton(
                      collection, locale, skeletons[i], symbolsToIgnore);
              if (!success) {
                  return false;
              }
          }
          return true;
      }
  
  
      /* package */
      static boolean addAmPmChars(@NonNull Collection<Character> collection,
                                  @Nullable Locale locale) {
          if (locale == null) {
              return false;
          }
          final String[] amPm = LocaleData.get(locale).amPm;
          for (int i = 0; i < amPm.length; i++) {
              for (int j = 0; j < amPm[i].length(); j++) {
                  final char ch = amPm[i].charAt(j);
                  if (Character.isBmpCodePoint(ch)) {
                      collection.add(Character.valueOf(ch));
                  } else {  // We don't support non-BMP characters.
                      return false;
                  }
              }
          }
          return true;
      }
  
      /* package */
      @NonNull
      static char[] collectionToArray(@NonNull Collection<Character> chars) {
          final char[] result = new char[chars.size()];
          int i = 0;
          for (Character ch : chars) {
              result[i++] = ch;
          }
          return result;
      }
  }
  