content: /*
   * Copyright (C) 2007 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.content.ContentResolver;
  import android.content.Context;
  import android.database.ContentObserver;
  import android.os.Handler;
  import android.provider.Settings;
  import android.provider.Settings.System;
  import android.text.Editable;
  import android.text.InputType;
  import android.text.NoCopySpan;
  import android.text.Selection;
  import android.text.SpanWatcher;
  import android.text.Spannable;
  import android.text.TextUtils;
  import android.view.KeyCharacterMap;
  import android.view.KeyEvent;
  import android.view.View;
  
  import java.lang.ref.WeakReference;
  
  /**
   * This is the key listener for typing normal text.  It delegates to
   * other key listeners appropriate to the current keyboard and language.
   * <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 class TextKeyListener extends BaseKeyListener implements SpanWatcher {
      private static TextKeyListener[] sInstance =
          new TextKeyListener[Capitalize.values().length * 2];
  
      /* package */ static final Object ACTIVE = new NoCopySpan.Concrete();
      /* package */ static final Object CAPPED = new NoCopySpan.Concrete();
      /* package */ static final Object INHIBIT_REPLACEMENT = new NoCopySpan.Concrete();
      /* package */ static final Object LAST_TYPED = new NoCopySpan.Concrete();
  
      private Capitalize mAutoCap;
      private boolean mAutoText;
  
      private int mPrefs;
      private boolean mPrefsInited;
  
      /* package */ static final int AUTO_CAP = 1;
      /* package */ static final int AUTO_TEXT = 2;
      /* package */ static final int AUTO_PERIOD = 4;
      /* package */ static final int SHOW_PASSWORD = 8;
      private WeakReference<ContentResolver> mResolver;
      private TextKeyListener.SettingsObserver mObserver;
  
      /**
       * Creates a new TextKeyListener with the specified capitalization
       * and correction properties.
       *
       * @param cap when, if ever, to automatically capitalize.
       * @param autotext whether to automatically do spelling corrections.
       */
      public TextKeyListener(Capitalize cap, boolean autotext) {
          mAutoCap = cap;
          mAutoText = autotext;
      }
  
      /**
       * Returns a new or existing instance with the specified capitalization
       * and correction properties.
       *
       * @param cap when, if ever, to automatically capitalize.
       * @param autotext whether to automatically do spelling corrections.
       */
      public static TextKeyListener getInstance(boolean autotext,
                                                Capitalize cap) {
          int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
  
          if (sInstance[off] == null) {
              sInstance[off] = new TextKeyListener(cap, autotext);
          }
  
          return sInstance[off];
      }
  
      /**
       * Returns a new or existing instance with no automatic capitalization
       * or correction.
       */
      public static TextKeyListener getInstance() {
          return getInstance(false, Capitalize.NONE);
      }
  
      /**
       * Returns whether it makes sense to automatically capitalize at the
       * specified position in the specified text, with the specified rules.
       *
       * @param cap the capitalization rules to consider.
       * @param cs the text in which an insertion is being made.
       * @param off the offset into that text where the insertion is being made.
       *
       * @return whether the character being inserted should be capitalized.
       */
      public static boolean shouldCap(Capitalize cap, CharSequence cs, int off) {
          int i;
          char c;
  
          if (cap == Capitalize.NONE) {
              return false;
          }
          if (cap == Capitalize.CHARACTERS) {
              return true;
          }
  
          return TextUtils.getCapsMode(cs, off, cap == Capitalize.WORDS
                  ? TextUtils.CAP_MODE_WORDS : TextUtils.CAP_MODE_SENTENCES)
                  != 0;
      }
  
      public int getInputType() {
          return makeTextContentType(mAutoCap, mAutoText);
      }
  
      @Override
      public boolean onKeyDown(View view, Editable content,
                               int keyCode, KeyEvent event) {
          KeyListener im = getKeyListener(event);
  
          return im.onKeyDown(view, content, keyCode, event);
      }
  
      @Override
      public boolean onKeyUp(View view, Editable content,
                             int keyCode, KeyEvent event) {
          KeyListener im = getKeyListener(event);
  
          return im.onKeyUp(view, content, keyCode, event);
      }
  
      @Override
      public boolean onKeyOther(View view, Editable content, KeyEvent event) {
          KeyListener im = getKeyListener(event);
  
          return im.onKeyOther(view, content, event);
      }
  
      /**
       * Clear all the input state (autotext, autocap, multitap, undo)
       * from the specified Editable, going beyond Editable.clear(), which
       * just clears the text but not the input state.
       *
       * @param e the buffer whose text and state are to be cleared.
       */
      public static void clear(Editable e) {
          e.clear();
          e.removeSpan(ACTIVE);
          e.removeSpan(CAPPED);
          e.removeSpan(INHIBIT_REPLACEMENT);
          e.removeSpan(LAST_TYPED);
  
          QwertyKeyListener.Replaced[] repl = e.getSpans(0, e.length(),
                                     QwertyKeyListener.Replaced.class);
          final int count = repl.length;
          for (int i = 0; i < count; i++) {
              e.removeSpan(repl[i]);
          }
      }
  
      public void onSpanAdded(Spannable s, Object what, int start, int end) { }
      public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
  
      public void onSpanChanged(Spannable s, Object what, int start, int end,
                                int st, int en) {
          if (what == Selection.SELECTION_END) {
              s.removeSpan(ACTIVE);
          }
      }
  
      private KeyListener getKeyListener(KeyEvent event) {
          KeyCharacterMap kmap = event.getKeyCharacterMap();
          int kind = kmap.getKeyboardType();
  
          if (kind == KeyCharacterMap.ALPHA) {
              return QwertyKeyListener.getInstance(mAutoText, mAutoCap);
          } else if (kind == KeyCharacterMap.NUMERIC) {
              return MultiTapKeyListener.getInstance(mAutoText, mAutoCap);
          } else if (kind == KeyCharacterMap.FULL
                  || kind == KeyCharacterMap.SPECIAL_FUNCTION) {
              // We consider special function keyboards full keyboards as a workaround for
              // devices that do not have built-in keyboards.  Applications may try to inject
              // key events using the built-in keyboard device id which may be configured as
              // a special function keyboard using a default key map.  Ideally, as of Honeycomb,
              // these applications should be modified to use KeyCharacterMap.VIRTUAL_KEYBOARD.
              return QwertyKeyListener.getInstanceForFullKeyboard();
          }
  
          return NullKeyListener.getInstance();
      }
  
      public enum Capitalize {
          NONE, SENTENCES, WORDS, CHARACTERS,
      }
  
      private static class NullKeyListener implements KeyListener
      {
          public int getInputType() {
              return InputType.TYPE_NULL;
          }
  
          public boolean onKeyDown(View view, Editable content,
                                   int keyCode, KeyEvent event) {
              return false;
          }
  
          public boolean onKeyUp(View view, Editable content, int keyCode,
                                          KeyEvent event) {
              return false;
          }
  
          public boolean onKeyOther(View view, Editable content, KeyEvent event) {
              return false;
          }
  
          public void clearMetaKeyState(View view, Editable content, int states) {
          }
  
          public static NullKeyListener getInstance() {
              if (sInstance != null)
                  return sInstance;
  
              sInstance = new NullKeyListener();
              return sInstance;
          }
  
          private static NullKeyListener sInstance;
      }
  
      public void release() {
          if (mResolver != null) {
              final ContentResolver contentResolver = mResolver.get();
              if (contentResolver != null) {
                  contentResolver.unregisterContentObserver(mObserver);
                  mResolver.clear();
              }
              mObserver = null;
              mResolver = null;
              mPrefsInited = false;
          }
      }
  
      private void initPrefs(Context context) {
          final ContentResolver contentResolver = context.getContentResolver();
          mResolver = new WeakReference<ContentResolver>(contentResolver);
          if (mObserver == null) {
              mObserver = new SettingsObserver();
              contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
          }
  
          updatePrefs(contentResolver);
          mPrefsInited = true;
      }
  
      private class SettingsObserver extends ContentObserver {
          public SettingsObserver() {
              super(new Handler());
          }
  
          @Override
          public void onChange(boolean selfChange) {
              if (mResolver != null) {
                  final ContentResolver contentResolver = mResolver.get();
                  if (contentResolver == null) {
                      mPrefsInited = false;
                  } else {
                      updatePrefs(contentResolver);
                  }
              } else {
                  mPrefsInited = false;
              }
          }
      }
  
      private void updatePrefs(ContentResolver resolver) {
          boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;
          boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;
          boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;
          boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;
  
          mPrefs = (cap ? AUTO_CAP : 0) |
                   (text ? AUTO_TEXT : 0) |
                   (period ? AUTO_PERIOD : 0) |
                   (pw ? SHOW_PASSWORD : 0);
      }
  
      /* package */ int getPrefs(Context context) {
          synchronized (this) {
              if (!mPrefsInited || mResolver.get() == null) {
                  initPrefs(context);
              }
          }
  
          return mPrefs;
      }
  }
  