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.UnsupportedAppUsage;
  import android.graphics.Rect;
  import android.os.Build;
  import android.os.Handler;
  import android.os.SystemClock;
  import android.text.Editable;
  import android.text.GetChars;
  import android.text.NoCopySpan;
  import android.text.Spannable;
  import android.text.Spanned;
  import android.text.TextUtils;
  import android.text.TextWatcher;
  import android.text.style.UpdateLayout;
  import android.view.View;
  
  import java.lang.ref.WeakReference;
  
  public class PasswordTransformationMethod
  implements TransformationMethod, TextWatcher
  {
      public CharSequence getTransformation(CharSequence source, View view) {
          if (source instanceof Spannable) {
              Spannable sp = (Spannable) source;
  
              /*
               * Remove any references to other views that may still be
               * attached.  This will happen when you flip the screen
               * while a password field is showing; there will still
               * be references to the old EditText in the text.
               */
              ViewReference[] vr = sp.getSpans(0, sp.length(),
                                               ViewReference.class);
              for (int i = 0; i < vr.length; i++) {
                  sp.removeSpan(vr[i]);
              }
  
              removeVisibleSpans(sp);
  
              sp.setSpan(new ViewReference(view), 0, 0,
                         Spannable.SPAN_POINT_POINT);
          }
  
          return new PasswordCharSequence(source);
      }
  
      public static PasswordTransformationMethod getInstance() {
          if (sInstance != null)
              return sInstance;
  
          sInstance = new PasswordTransformationMethod();
          return sInstance;
      }
  
      public void beforeTextChanged(CharSequence s, int start,
                                    int count, int after) {
          // This callback isn't used.
      }
  
      public void onTextChanged(CharSequence s, int start,
                                int before, int count) {
          if (s instanceof Spannable) {
              Spannable sp = (Spannable) s;
              ViewReference[] vr = sp.getSpans(0, s.length(),
                                               ViewReference.class);
              if (vr.length == 0) {
                  return;
              }
  
              /*
               * There should generally only be one ViewReference in the text,
               * but make sure to look through all of them if necessary in case
               * something strange is going on.  (We might still end up with
               * multiple ViewReferences if someone moves text from one password
               * field to another.)
               */
              View v = null;
              for (int i = 0; v == null && i < vr.length; i++) {
                  v = vr[i].get();
              }
  
              if (v == null) {
                  return;
              }
  
              int pref = TextKeyListener.getInstance().getPrefs(v.getContext());
              if ((pref & TextKeyListener.SHOW_PASSWORD) != 0) {
                  if (count > 0) {
                      removeVisibleSpans(sp);
  
                      if (count == 1) {
                          sp.setSpan(new Visible(sp, this), start, start + count,
                                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                      }
                  }
              }
          }
      }
  
      public void afterTextChanged(Editable s) {
          // This callback isn't used.
      }
  
      public void onFocusChanged(View view, CharSequence sourceText,
                                 boolean focused, int direction,
                                 Rect previouslyFocusedRect) {
          if (!focused) {
              if (sourceText instanceof Spannable) {
                  Spannable sp = (Spannable) sourceText;
  
                  removeVisibleSpans(sp);
              }
          }
      }
  
      private static void removeVisibleSpans(Spannable sp) {
          Visible[] old = sp.getSpans(0, sp.length(), Visible.class);
          for (int i = 0; i < old.length; i++) {
              sp.removeSpan(old[i]);
          }
      }
  
      private static class PasswordCharSequence
      implements CharSequence, GetChars
      {
          public PasswordCharSequence(CharSequence source) {
              mSource = source;
          }
  
          public int length() {
              return mSource.length();
          }
  
          public char charAt(int i) {
              if (mSource instanceof Spanned) {
                  Spanned sp = (Spanned) mSource;
  
                  int st = sp.getSpanStart(TextKeyListener.ACTIVE);
                  int en = sp.getSpanEnd(TextKeyListener.ACTIVE);
  
                  if (i >= st && i < en) {
                      return mSource.charAt(i);
                  }
  
                  Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
  
                  for (int a = 0; a < visible.length; a++) {
                      if (sp.getSpanStart(visible[a].mTransformer) >= 0) {
                          st = sp.getSpanStart(visible[a]);
                          en = sp.getSpanEnd(visible[a]);
  
                          if (i >= st && i < en) {
                              return mSource.charAt(i);
                          }
                      }
                  }
              }
  
              return DOT;
          }
  
          public CharSequence subSequence(int start, int end) {
              char[] buf = new char[end - start];
  
              getChars(start, end, buf, 0);
              return new String(buf);
          }
  
          public String toString() {
              return subSequence(0, length()).toString();
          }
  
          public void getChars(int start, int end, char[] dest, int off) {
              TextUtils.getChars(mSource, start, end, dest, off);
  
              int st = -1, en = -1;
              int nvisible = 0;
              int[] starts = null, ends = null;
  
              if (mSource instanceof Spanned) {
                  Spanned sp = (Spanned) mSource;
  
                  st = sp.getSpanStart(TextKeyListener.ACTIVE);
                  en = sp.getSpanEnd(TextKeyListener.ACTIVE);
  
                  Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
                  nvisible = visible.length;
                  starts = new int[nvisible];
                  ends = new int[nvisible];
  
                  for (int i = 0; i < nvisible; i++) {
                      if (sp.getSpanStart(visible[i].mTransformer) >= 0) {
                          starts[i] = sp.getSpanStart(visible[i]);
                          ends[i] = sp.getSpanEnd(visible[i]);
                      }
                  }
              }
  
              for (int i = start; i < end; i++) {
                  if (! (i >= st && i < en)) {
                      boolean visible = false;
  
                      for (int a = 0; a < nvisible; a++) {
                          if (i >= starts[a] && i < ends[a]) {
                              visible = true;
                              break;
                          }
                      }
  
                      if (!visible) {
                          dest[i - start + off] = DOT;
                      }
                  }
              }
          }
  
          private CharSequence mSource;
      }
  
      private static class Visible
      extends Handler
      implements UpdateLayout, Runnable
      {
          public Visible(Spannable sp, PasswordTransformationMethod ptm) {
              mText = sp;
              mTransformer = ptm;
              postAtTime(this, SystemClock.uptimeMillis() + 1500);
          }
  
          public void run() {
              mText.removeSpan(this);
          }
  
          private Spannable mText;
          private PasswordTransformationMethod mTransformer;
      }
  
      /**
       * Used to stash a reference back to the View in the Editable so we
       * can use it to check the settings.
       */
      private static class ViewReference extends WeakReference<View>
              implements NoCopySpan {
          public ViewReference(View v) {
              super(v);
          }
      }
  
      @UnsupportedAppUsage
      private static PasswordTransformationMethod sInstance;
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      private static char DOT = '\u2022';
  }
  