14e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek/*
24e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * Copyright (C) 2014 The Android Open Source Project
34e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek *
44e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * Licensed under the Apache License, Version 2.0 (the "License");
54e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * you may not use this file except in compliance with the License.
64e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * You may obtain a copy of the License at
74e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek *
84e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek *      http://www.apache.org/licenses/LICENSE-2.0
94e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek *
104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * Unless required by applicable law or agreed to in writing, software
114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * distributed under the License is distributed on an "AS IS" BASIS,
124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * See the License for the specific language governing permissions and
144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * limitations under the License
154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek */
164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekpackage com.android.keyguard;
184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.animation.Animator;
204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.animation.AnimatorListenerAdapter;
214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.animation.AnimatorSet;
224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.animation.ValueAnimator;
234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.content.Context;
244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.content.res.TypedArray;
254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.graphics.Canvas;
264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.graphics.Paint;
274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.graphics.Rect;
284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.graphics.Typeface;
294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.os.PowerManager;
304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.os.SystemClock;
314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.provider.Settings;
324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.util.AttributeSet;
334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.view.View;
344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.view.animation.AnimationUtils;
354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.view.animation.Interpolator;
364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport java.util.ArrayList;
384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport java.util.Stack;
394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek/**
414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * A View similar to a textView which contains password text and can animate when the text is
424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * changed
434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek */
444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekpublic class PasswordTextView extends View {
454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long APPEAR_DURATION = 160;
494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long DISAPPEAR_DURATION = 160;
504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long RESET_DELAY_PER_ELEMENT = 40;
514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long RESET_MAX_DELAY = 200;
524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The overlap between the text disappearing and the dot appearing animation
554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION = 130;
574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The duration the text needs to stay there at least before it can morph into a dot
604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long TEXT_REST_DURATION_AFTER_APPEAR = 100;
624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The duration the text should be visible, starting with the appear animation
654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long TEXT_VISIBILITY_DURATION = 1300;
674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The position in time from [0,1] where the overshoot should be finished and the settle back
704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * animation of the dot should start
714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final float OVERSHOOT_TIME_POSITION = 0.5f;
734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The raw text size, will be multiplied by the scaled density when drawn
764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private final int mTextHeightRaw;
784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private ArrayList<CharState> mTextChars = new ArrayList<>();
794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private String mText = "";
804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Stack<CharState> mCharPool = new Stack<>();
814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private int mDotSize;
824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private PowerManager mPM;
834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private int mCharPadding;
844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private final Paint mDrawPaint = new Paint();
854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Interpolator mAppearInterpolator;
864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Interpolator mDisappearInterpolator;
874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Interpolator mFastOutSlowInInterpolator;
884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private boolean mShowPassword;
894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public PasswordTextView(Context context) {
914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        this(context, null);
924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public PasswordTextView(Context context, AttributeSet attrs) {
954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        this(context, attrs, 0);
964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        this(context, attrs, defStyleAttr, 0);
1004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr,
1034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            int defStyleRes) {
1044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        super(context, attrs, defStyleAttr, defStyleRes);
1054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        setFocusableInTouchMode(true);
1064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        setFocusable(true);
1074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
1084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        try {
1094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            mTextHeightRaw = a.getInt(R.styleable.PasswordTextView_scaledTextSize, 0);
1104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        } finally {
1114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            a.recycle();
1124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
1134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
1144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setTextAlign(Paint.Align.CENTER);
1154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setColor(0xffffffff);
1164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setTypeface(Typeface.create("sans-serif-light", 0));
1174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDotSize = getContext().getResources().getDimensionPixelSize(R.dimen.password_dot_size);
1184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mCharPadding = getContext().getResources().getDimensionPixelSize(R.dimen
1194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                .password_char_padding);
1204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mShowPassword = Settings.System.getInt(mContext.getContentResolver(),
1214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                Settings.System.TEXT_SHOW_PASSWORD, 1) == 1;
1224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
1234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                android.R.interpolator.linear_out_slow_in);
1244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
1254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                android.R.interpolator.fast_out_linear_in);
1264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
1274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                android.R.interpolator.fast_out_slow_in);
1284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
1294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    @Override
1324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    protected void onDraw(Canvas canvas) {
1334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float totalDrawingWidth = getDrawingWidth();
1344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
1354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int length = mTextChars.size();
1364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Rect bounds = getCharBounds();
1374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int charHeight = (bounds.bottom - bounds.top);
1384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float yPosition = getHeight() / 2;
1394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float charLength = bounds.right - bounds.left;
1404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        for (int i = 0; i < length; i++) {
1414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState charState = mTextChars.get(i);
1424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            float charWidth = charState.draw(canvas, currentDrawPosition, charHeight, yPosition,
1434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    charLength);
1444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentDrawPosition += charWidth;
1454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
1464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1486380482d081e557164393417370e0c5da22a5bdaSelim Cinek    @Override
1496380482d081e557164393417370e0c5da22a5bdaSelim Cinek    public boolean hasOverlappingRendering() {
1506380482d081e557164393417370e0c5da22a5bdaSelim Cinek        return false;
1516380482d081e557164393417370e0c5da22a5bdaSelim Cinek    }
1526380482d081e557164393417370e0c5da22a5bdaSelim Cinek
1534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Rect getCharBounds() {
1544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float textHeight = mTextHeightRaw * getResources().getDisplayMetrics().scaledDensity;
1554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setTextSize(textHeight);
1564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Rect bounds = new Rect();
1574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.getTextBounds("0", 0, 1, bounds);
1584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        return bounds;
1594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private float getDrawingWidth() {
1624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int width = 0;
1634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int length = mTextChars.size();
1644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Rect bounds = getCharBounds();
1654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int charLength = bounds.right - bounds.left;
1664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        for (int i = 0; i < length; i++) {
1674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState charState = mTextChars.get(i);
1684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (i != 0) {
1694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                width += mCharPadding * charState.currentWidthFactor;
1704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
1714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            width += charLength * charState.currentWidthFactor;
1724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
1734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        return width;
1744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public void append(char c) {
1784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int visibleChars = mTextChars.size();
1794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mText = mText + c;
1804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int newLength = mText.length();
1814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        CharState charState;
1824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if (newLength > visibleChars) {
1834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState = obtainCharState(c);
1844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            mTextChars.add(charState);
1854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        } else {
1864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState = mTextChars.get(newLength - 1);
1874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState.whichChar = c;
1884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
1894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        charState.startAppearAnimation();
1904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        // ensure that the previous element is being swapped
1924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if (newLength > 1) {
1934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState previousState = mTextChars.get(newLength - 2);
1944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (previousState.isDotSwapPending) {
1954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                previousState.swapToDotWhenAppearFinished();
1964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
1974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
1984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        userActivity();
1994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private void userActivity() {
2024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mPM.userActivity(SystemClock.uptimeMillis(), false);
2034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public void deleteLastChar() {
2064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int length = mText.length();
2074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if (length > 0) {
2084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            mText = mText.substring(0, length - 1);
2094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState charState = mTextChars.get(length - 1);
2104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState.startRemoveAnimation(0, 0);
2114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        userActivity();
2134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public String getText() {
2164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        return mText;
2174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private CharState obtainCharState(char c) {
2204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        CharState charState;
2214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if(mCharPool.isEmpty()) {
2224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState = new CharState();
2234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        } else {
2244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState = mCharPool.pop();
2254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState.reset();
2264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        charState.whichChar = c;
2284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        return charState;
2294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public void reset(boolean animated) {
2324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mText = "";
2334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int length = mTextChars.size();
2344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int middleIndex = (length - 1) / 2;
2354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        long delayPerElement = RESET_DELAY_PER_ELEMENT;
2364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        for (int i = 0; i < length; i++) {
2374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState charState = mTextChars.get(i);
2384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (animated) {
2394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                int delayIndex;
2404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                if (i <= middleIndex) {
2414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    delayIndex = i * 2;
2424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                } else {
2434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    int distToMiddle = i - middleIndex;
2444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    delayIndex = (length - 1) - (distToMiddle - 1) * 2;
2454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                }
2464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                long startDelay = delayIndex * delayPerElement;
2474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startDelay = Math.min(startDelay, RESET_MAX_DELAY);
2484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                long maxDelay = delayPerElement * (length - 1);
2494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
2504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                charState.startRemoveAnimation(startDelay, maxDelay);
2514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                charState.removeDotSwapCallbacks();
2524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            } else {
2534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                mCharPool.push(charState);
2544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
2554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if (!animated) {
2574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            mTextChars.clear();
2584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private class CharState {
2624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        char whichChar;
2634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        ValueAnimator textAnimator;
2644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        boolean textAnimationIsGrowing;
2654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator dotAnimator;
2664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        boolean dotAnimationIsGrowing;
2674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        ValueAnimator widthAnimator;
2684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        boolean widthAnimationIsGrowing;
2694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentTextSizeFactor;
2704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentDotSizeFactor;
2714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentWidthFactor;
2724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        boolean isDotSwapPending;
2734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentTextTranslationY = 1.0f;
2744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        ValueAnimator textTranslateAnimator;
2754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener removeEndListener = new AnimatorListenerAdapter() {
2774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            private boolean mCancelled;
2784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
2794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationCancel(Animator animation) {
2804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                mCancelled = true;
2814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
2824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
2844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
2854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                if (!mCancelled) {
2864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    mTextChars.remove(CharState.this);
2874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    mCharPool.push(CharState.this);
2884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    reset();
2894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    cancelAnimator(textTranslateAnimator);
2904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    textTranslateAnimator = null;
2914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                }
2924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
2934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
2954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationStart(Animator animation) {
2964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                mCancelled = false;
2974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
2984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
2994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener dotFinishListener = new AnimatorListenerAdapter() {
3014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
3034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                dotAnimator = null;
3044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener textFinishListener = new AnimatorListenerAdapter() {
3084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
3104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textAnimator = null;
3114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener textTranslateFinishListener = new AnimatorListenerAdapter() {
3154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
3174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator = null;
3184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener widthFinishListener = new AnimatorListenerAdapter() {
3224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
3244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                widthAnimator = null;
3254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private ValueAnimator.AnimatorUpdateListener dotSizeUpdater
3294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                = new ValueAnimator.AnimatorUpdateListener() {
3304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
3324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                currentDotSizeFactor = (float) animation.getAnimatedValue();
3334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                invalidate();
3344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private ValueAnimator.AnimatorUpdateListener textSizeUpdater
3384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                = new ValueAnimator.AnimatorUpdateListener() {
3394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
3414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                currentTextSizeFactor = (float) animation.getAnimatedValue();
3424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                invalidate();
3434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private ValueAnimator.AnimatorUpdateListener textTranslationUpdater
3474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                = new ValueAnimator.AnimatorUpdateListener() {
3484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
3504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                currentTextTranslationY = (float) animation.getAnimatedValue();
3514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                invalidate();
3524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private ValueAnimator.AnimatorUpdateListener widthUpdater
3564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                = new ValueAnimator.AnimatorUpdateListener() {
3574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
3594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                currentWidthFactor = (float) animation.getAnimatedValue();
3604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                invalidate();
3614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private Runnable dotSwapperRunnable = new Runnable() {
3654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void run() {
3674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                performSwap();
3684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                isDotSwapPending = false;
3694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
3714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        void reset() {
3734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            whichChar = 0;
3744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentTextSizeFactor = 0.0f;
3754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentDotSizeFactor = 0.0f;
3764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentWidthFactor = 0.0f;
3774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(textAnimator);
3784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator = null;
3794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(dotAnimator);
3804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            dotAnimator = null;
3814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(widthAnimator);
3824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator = null;
3834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentTextTranslationY = 1.0f;
3844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            removeDotSwapCallbacks();
3854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
3864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        void startRemoveAnimation(long startDelay, long widthDelay) {
3884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null)
3894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    || (dotAnimator != null && dotAnimationIsGrowing);
3904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean textNeedsAnimation = (currentTextSizeFactor > 0.0f && textAnimator == null)
3914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    || (textAnimator != null && textAnimationIsGrowing);
3924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean widthNeedsAnimation = (currentWidthFactor > 0.0f && widthAnimator == null)
3934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    || (widthAnimator != null && widthAnimationIsGrowing);
3944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (dotNeedsAnimation) {
3954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startDotDisappearAnimation(startDelay);
3964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textNeedsAnimation) {
3984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startTextDisappearAnimation(startDelay);
3994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (widthNeedsAnimation) {
4014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startWidthDisappearAnimation(widthDelay);
4024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        void startAppearAnimation() {
4064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean dotNeedsAnimation = !mShowPassword
4074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    && (dotAnimator == null || !dotAnimationIsGrowing);
4084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean textNeedsAnimation = mShowPassword
4094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    && (textAnimator == null || !textAnimationIsGrowing);
4104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean widthNeedsAnimation = (widthAnimator == null || !widthAnimationIsGrowing);
4114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (dotNeedsAnimation) {
4124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startDotAppearAnimation(0);
4134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textNeedsAnimation) {
4154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startTextAppearAnimation();
4164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (widthNeedsAnimation) {
4184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startWidthAppearAnimation();
4194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (mShowPassword) {
4214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                postDotSwap(TEXT_VISIBILITY_DURATION);
4224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        /**
4264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         * Posts a runnable which ensures that the text will be replaced by a dot after {@link
4274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         * com.android.keyguard.PasswordTextView#TEXT_VISIBILITY_DURATION}.
4284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         */
4294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void postDotSwap(long delay) {
4304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            removeDotSwapCallbacks();
4314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            postDelayed(dotSwapperRunnable, delay);
4324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            isDotSwapPending = true;
4334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void removeDotSwapCallbacks() {
4364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            removeCallbacks(dotSwapperRunnable);
4374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            isDotSwapPending = false;
4384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        void swapToDotWhenAppearFinished() {
4414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            removeDotSwapCallbacks();
4424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textAnimator != null) {
4434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                long remainingDuration = textAnimator.getDuration()
4444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        - textAnimator.getCurrentPlayTime();
4454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                postDotSwap(remainingDuration + TEXT_REST_DURATION_AFTER_APPEAR);
4464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            } else {
4474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                performSwap();
4484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void performSwap() {
4524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            startTextDisappearAnimation(0);
4534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            startDotAppearAnimation(DISAPPEAR_DURATION
4544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
4554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startWidthDisappearAnimation(long widthDelay) {
4584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(widthAnimator);
4594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 0.0f);
4604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addUpdateListener(widthUpdater);
4614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addListener(widthFinishListener);
4624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addListener(removeEndListener);
4634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.setDuration((long) (DISAPPEAR_DURATION * currentWidthFactor));
4644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.setStartDelay(widthDelay);
4654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.start();
4664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimationIsGrowing = false;
4674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startTextDisappearAnimation(long startDelay) {
4704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(textAnimator);
4714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 0.0f);
4724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.addUpdateListener(textSizeUpdater);
4734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.addListener(textFinishListener);
4744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setInterpolator(mDisappearInterpolator);
4754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setDuration((long) (DISAPPEAR_DURATION * currentTextSizeFactor));
4764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setStartDelay(startDelay);
4774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.start();
4784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimationIsGrowing = false;
4794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startDotDisappearAnimation(long startDelay) {
4824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(dotAnimator);
4834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            ValueAnimator animator = ValueAnimator.ofFloat(currentDotSizeFactor, 0.0f);
4844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.addUpdateListener(dotSizeUpdater);
4854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.addListener(dotFinishListener);
4864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.setInterpolator(mDisappearInterpolator);
4874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            long duration = (long) (DISAPPEAR_DURATION * Math.min(currentDotSizeFactor, 1.0f));
4884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.setDuration(duration);
4894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.setStartDelay(startDelay);
4904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.start();
4914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            dotAnimator = animator;
4924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            dotAnimationIsGrowing = false;
4934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startWidthAppearAnimation() {
4964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(widthAnimator);
4974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 1.0f);
4984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addUpdateListener(widthUpdater);
4994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addListener(widthFinishListener);
5004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentWidthFactor)));
5014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.start();
5024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimationIsGrowing = true;
5034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startTextAppearAnimation() {
5064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(textAnimator);
5074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 1.0f);
5084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.addUpdateListener(textSizeUpdater);
5094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.addListener(textFinishListener);
5104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setInterpolator(mAppearInterpolator);
5114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentTextSizeFactor)));
5124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.start();
5134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimationIsGrowing = true;
5144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            // handle translation
5164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textTranslateAnimator == null) {
5174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
5184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.addUpdateListener(textTranslationUpdater);
5194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.addListener(textTranslateFinishListener);
5204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.setInterpolator(mAppearInterpolator);
5214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.setDuration(APPEAR_DURATION);
5224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.start();
5234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startDotAppearAnimation(long delay) {
5274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(dotAnimator);
5284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (!mShowPassword) {
5294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                // We perform an overshoot animation
5304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                ValueAnimator overShootAnimator = ValueAnimator.ofFloat(currentDotSizeFactor,
5314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        DOT_OVERSHOOT_FACTOR);
5324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                overShootAnimator.addUpdateListener(dotSizeUpdater);
5334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                overShootAnimator.setInterpolator(mAppearInterpolator);
5344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                long overShootDuration = (long) (DOT_APPEAR_DURATION_OVERSHOOT
5354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        * OVERSHOOT_TIME_POSITION);
5364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                overShootAnimator.setDuration(overShootDuration);
5374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                ValueAnimator settleBackAnimator = ValueAnimator.ofFloat(DOT_OVERSHOOT_FACTOR,
5384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        1.0f);
5394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                settleBackAnimator.addUpdateListener(dotSizeUpdater);
5404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                settleBackAnimator.setDuration(DOT_APPEAR_DURATION_OVERSHOOT - overShootDuration);
5414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                settleBackAnimator.addListener(dotFinishListener);
5424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                AnimatorSet animatorSet = new AnimatorSet();
5434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                animatorSet.playSequentially(overShootAnimator, settleBackAnimator);
5444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                animatorSet.setStartDelay(delay);
5454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                animatorSet.start();
5464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                dotAnimator = animatorSet;
5474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            } else {
5484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                ValueAnimator growAnimator = ValueAnimator.ofFloat(currentDotSizeFactor, 1.0f);
5494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.addUpdateListener(dotSizeUpdater);
5504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.setDuration((long) (APPEAR_DURATION * (1.0f - currentDotSizeFactor)));
5514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.addListener(dotFinishListener);
5524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.setStartDelay(delay);
5534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.start();
5544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                dotAnimator = growAnimator;
5554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            dotAnimationIsGrowing = true;
5574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void cancelAnimator(Animator animator) {
5604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (animator != null) {
5614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                animator.cancel();
5624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        /**
5664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         * Draw this char to the canvas.
5674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         *
5684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         * @return The width this character contributes, including padding.
5694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         */
5704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        public float draw(Canvas canvas, float currentDrawPosition, int charHeight, float yPosition,
5714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                float charLength) {
5724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean textVisible = currentTextSizeFactor > 0;
5734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean dotVisible = currentDotSizeFactor > 0;
5744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            float charWidth = charLength * currentWidthFactor;
5754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textVisible) {
5764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                float currYPosition = yPosition + charHeight / 2.0f * currentTextSizeFactor
5774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        + charHeight * currentTextTranslationY * 0.8f;
5784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.save();
5794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                float centerX = currentDrawPosition + charWidth / 2;
5804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.translate(centerX, currYPosition);
5814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.scale(currentTextSizeFactor, currentTextSizeFactor);
5824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.drawText(Character.toString(whichChar), 0, 0, mDrawPaint);
5834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.restore();
5844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (dotVisible) {
5864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.save();
5874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                float centerX = currentDrawPosition + charWidth / 2;
5884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.translate(centerX, yPosition);
5894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.drawCircle(0, 0, mDotSize / 2 * currentDotSizeFactor, mDrawPaint);
5904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.restore();
5914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            return charWidth + mCharPadding * currentWidthFactor;
5934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
5954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek}
596