PasswordTextView.java revision 90427507882de8ddbd178c6466896deb4969f5bc
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;
31a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roosimport android.os.UserHandle;
324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.provider.Settings;
33a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roosimport android.text.InputType;
34a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roosimport android.text.TextUtils;
354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.util.AttributeSet;
3690427507882de8ddbd178c6466896deb4969f5bcEvan Roskyimport android.view.Gravity;
374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.view.View;
38a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roosimport android.view.accessibility.AccessibilityEvent;
39a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roosimport android.view.accessibility.AccessibilityManager;
40a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roosimport android.view.accessibility.AccessibilityNodeInfo;
414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.view.animation.AnimationUtils;
424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport android.view.animation.Interpolator;
434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport java.util.ArrayList;
454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekimport java.util.Stack;
464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek/**
484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * A View similar to a textView which contains password text and can animate when the text is
494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek * changed
504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek */
514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinekpublic class PasswordTextView extends View {
524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long APPEAR_DURATION = 160;
564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long DISAPPEAR_DURATION = 160;
574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long RESET_DELAY_PER_ELEMENT = 40;
584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long RESET_MAX_DELAY = 200;
594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The overlap between the text disappearing and the dot appearing animation
624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION = 130;
644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The duration the text needs to stay there at least before it can morph into a dot
674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long TEXT_REST_DURATION_AFTER_APPEAR = 100;
694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The duration the text should be visible, starting with the appear animation
724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final long TEXT_VISIBILITY_DURATION = 1300;
744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The position in time from [0,1] where the overshoot should be finished and the settle back
774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * animation of the dot should start
784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private static final float OVERSHOOT_TIME_POSITION = 0.5f;
804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    /**
824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     * The raw text size, will be multiplied by the scaled density when drawn
834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek     */
844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private final int mTextHeightRaw;
8590427507882de8ddbd178c6466896deb4969f5bcEvan Rosky    private final int mGravity;
864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private ArrayList<CharState> mTextChars = new ArrayList<>();
874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private String mText = "";
884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Stack<CharState> mCharPool = new Stack<>();
894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private int mDotSize;
904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private PowerManager mPM;
914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private int mCharPadding;
924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private final Paint mDrawPaint = new Paint();
934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Interpolator mAppearInterpolator;
944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Interpolator mDisappearInterpolator;
954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Interpolator mFastOutSlowInInterpolator;
964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private boolean mShowPassword;
9709eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia    private UserActivityListener mUserActivityListener;
9809eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia
9909eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia    public interface UserActivityListener {
10009eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia        void onUserActivity();
10109eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia    }
1024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public PasswordTextView(Context context) {
1044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        this(context, null);
1054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public PasswordTextView(Context context, AttributeSet attrs) {
1084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        this(context, attrs, 0);
1094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
1124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        this(context, attrs, defStyleAttr, 0);
1134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr,
1164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            int defStyleRes) {
1174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        super(context, attrs, defStyleAttr, defStyleRes);
1184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        setFocusableInTouchMode(true);
1194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        setFocusable(true);
1204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
1214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        try {
1224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            mTextHeightRaw = a.getInt(R.styleable.PasswordTextView_scaledTextSize, 0);
12390427507882de8ddbd178c6466896deb4969f5bcEvan Rosky            mGravity = a.getInt(R.styleable.PasswordTextView_android_gravity, Gravity.CENTER);
12490427507882de8ddbd178c6466896deb4969f5bcEvan Rosky            mDotSize = a.getDimensionPixelSize(R.styleable.PasswordTextView_dotSize,
12590427507882de8ddbd178c6466896deb4969f5bcEvan Rosky                    getContext().getResources().getDimensionPixelSize(R.dimen.password_dot_size));
12690427507882de8ddbd178c6466896deb4969f5bcEvan Rosky            mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding,
12790427507882de8ddbd178c6466896deb4969f5bcEvan Rosky                    getContext().getResources().getDimensionPixelSize(
12890427507882de8ddbd178c6466896deb4969f5bcEvan Rosky                            R.dimen.password_char_padding));
1294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        } finally {
1304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            a.recycle();
1314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
1324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
1334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setTextAlign(Paint.Align.CENTER);
1344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setColor(0xffffffff);
1354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setTypeface(Typeface.create("sans-serif-light", 0));
1364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mShowPassword = Settings.System.getInt(mContext.getContentResolver(),
1374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                Settings.System.TEXT_SHOW_PASSWORD, 1) == 1;
1384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
1394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                android.R.interpolator.linear_out_slow_in);
1404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
1414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                android.R.interpolator.fast_out_linear_in);
1424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
1434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                android.R.interpolator.fast_out_slow_in);
1444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
1454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    @Override
1484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    protected void onDraw(Canvas canvas) {
1494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float totalDrawingWidth = getDrawingWidth();
15090427507882de8ddbd178c6466896deb4969f5bcEvan Rosky        float currentDrawPosition;
15190427507882de8ddbd178c6466896deb4969f5bcEvan Rosky        if ((mGravity & Gravity.START) != 0) {
15290427507882de8ddbd178c6466896deb4969f5bcEvan Rosky            if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
15390427507882de8ddbd178c6466896deb4969f5bcEvan Rosky                currentDrawPosition = getWidth() - getPaddingRight() - totalDrawingWidth;
15490427507882de8ddbd178c6466896deb4969f5bcEvan Rosky            } else {
15590427507882de8ddbd178c6466896deb4969f5bcEvan Rosky                currentDrawPosition = getPaddingLeft();
15690427507882de8ddbd178c6466896deb4969f5bcEvan Rosky            }
15790427507882de8ddbd178c6466896deb4969f5bcEvan Rosky        } else {
15890427507882de8ddbd178c6466896deb4969f5bcEvan Rosky            currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
15990427507882de8ddbd178c6466896deb4969f5bcEvan Rosky        }
1604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int length = mTextChars.size();
1614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Rect bounds = getCharBounds();
1624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int charHeight = (bounds.bottom - bounds.top);
16390427507882de8ddbd178c6466896deb4969f5bcEvan Rosky        float yPosition =
16490427507882de8ddbd178c6466896deb4969f5bcEvan Rosky                (getHeight() - getPaddingBottom() - getPaddingTop()) / 2 + getPaddingTop();
16590427507882de8ddbd178c6466896deb4969f5bcEvan Rosky        canvas.clipRect(getPaddingLeft(), getPaddingTop(),
16690427507882de8ddbd178c6466896deb4969f5bcEvan Rosky                getWidth()-getPaddingRight(), getHeight()-getPaddingBottom());
1674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float charLength = bounds.right - bounds.left;
1684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        for (int i = 0; i < length; i++) {
1694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState charState = mTextChars.get(i);
1704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            float charWidth = charState.draw(canvas, currentDrawPosition, charHeight, yPosition,
1714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    charLength);
1724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentDrawPosition += charWidth;
1734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
1744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1766380482d081e557164393417370e0c5da22a5bdaSelim Cinek    @Override
1776380482d081e557164393417370e0c5da22a5bdaSelim Cinek    public boolean hasOverlappingRendering() {
1786380482d081e557164393417370e0c5da22a5bdaSelim Cinek        return false;
1796380482d081e557164393417370e0c5da22a5bdaSelim Cinek    }
1806380482d081e557164393417370e0c5da22a5bdaSelim Cinek
1814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private Rect getCharBounds() {
1824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float textHeight = mTextHeightRaw * getResources().getDisplayMetrics().scaledDensity;
1834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.setTextSize(textHeight);
1844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Rect bounds = new Rect();
1854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mDrawPaint.getTextBounds("0", 0, 1, bounds);
1864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        return bounds;
1874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
1884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
1894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private float getDrawingWidth() {
1904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int width = 0;
1914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int length = mTextChars.size();
1924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Rect bounds = getCharBounds();
1934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int charLength = bounds.right - bounds.left;
1944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        for (int i = 0; i < length; i++) {
1954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState charState = mTextChars.get(i);
1964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (i != 0) {
1974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                width += mCharPadding * charState.currentWidthFactor;
1984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
1994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            width += charLength * charState.currentWidthFactor;
2004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        return width;
2024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public void append(char c) {
2064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int visibleChars = mTextChars.size();
207a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        String textbefore = mText;
2084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mText = mText + c;
2094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int newLength = mText.length();
2104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        CharState charState;
2114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if (newLength > visibleChars) {
2124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState = obtainCharState(c);
2134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            mTextChars.add(charState);
2144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        } else {
2154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState = mTextChars.get(newLength - 1);
2164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState.whichChar = c;
2174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        charState.startAppearAnimation();
2194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        // ensure that the previous element is being swapped
2214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if (newLength > 1) {
2224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState previousState = mTextChars.get(newLength - 2);
2234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (previousState.isDotSwapPending) {
2244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                previousState.swapToDotWhenAppearFinished();
2254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
2264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        userActivity();
228a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
2294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
23109eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia    public void setUserActivityListener(UserActivityListener userActivitiListener) {
23209eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia        mUserActivityListener = userActivitiListener;
23309eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia    }
23409eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia
2354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private void userActivity() {
2364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mPM.userActivity(SystemClock.uptimeMillis(), false);
23709eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia        if (mUserActivityListener != null) {
23809eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia            mUserActivityListener.onUserActivity();
23909eb0337b760c74d73a614edfdc7eaa6e083a29fXiyuan Xia        }
2404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public void deleteLastChar() {
2434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int length = mText.length();
244a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        String textbefore = mText;
2454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if (length > 0) {
2464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            mText = mText.substring(0, length - 1);
2474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState charState = mTextChars.get(length - 1);
2484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState.startRemoveAnimation(0, 0);
2494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        userActivity();
251a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
2524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public String getText() {
2554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        return mText;
2564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private CharState obtainCharState(char c) {
2594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        CharState charState;
2604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if(mCharPool.isEmpty()) {
2614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState = new CharState();
2624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        } else {
2634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState = mCharPool.pop();
2644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            charState.reset();
2654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        charState.whichChar = c;
2674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        return charState;
2684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
2694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
2704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    public void reset(boolean animated) {
271a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        String textbefore = mText;
2724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        mText = "";
2734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int length = mTextChars.size();
2744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        int middleIndex = (length - 1) / 2;
2754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        long delayPerElement = RESET_DELAY_PER_ELEMENT;
2764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        for (int i = 0; i < length; i++) {
2774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            CharState charState = mTextChars.get(i);
2784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (animated) {
2794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                int delayIndex;
2804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                if (i <= middleIndex) {
2814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    delayIndex = i * 2;
2824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                } else {
2834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    int distToMiddle = i - middleIndex;
2844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    delayIndex = (length - 1) - (distToMiddle - 1) * 2;
2854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                }
2864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                long startDelay = delayIndex * delayPerElement;
2874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startDelay = Math.min(startDelay, RESET_MAX_DELAY);
2884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                long maxDelay = delayPerElement * (length - 1);
2894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
2904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                charState.startRemoveAnimation(startDelay, maxDelay);
2914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                charState.removeDotSwapCallbacks();
2924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            } else {
2934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                mCharPool.push(charState);
2944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
2954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
2964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        if (!animated) {
2974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            mTextChars.clear();
2984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
299a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
300a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    }
301a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
302a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
303a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos                                                   int removedCount, int addedCount) {
304a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        if (AccessibilityManager.getInstance(mContext).isEnabled() &&
305a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos                (isFocused() || isSelected() && isShown())) {
306a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            if (!shouldSpeakPasswordsForAccessibility()) {
307a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos                beforeText = null;
308a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            }
309a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            AccessibilityEvent event =
310a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
311a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            event.setFromIndex(fromIndex);
312a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            event.setRemovedCount(removedCount);
313a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            event.setAddedCount(addedCount);
314a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            event.setBeforeText(beforeText);
315a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            event.setPassword(true);
316a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            sendAccessibilityEventUnchecked(event);
317a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        }
318a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    }
319a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
320a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    @Override
321a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
322a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        super.onInitializeAccessibilityEvent(event);
323a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
324a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        event.setClassName(PasswordTextView.class.getName());
325a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        event.setPassword(true);
326a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    }
327a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
328a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    @Override
329a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
330a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        super.onPopulateAccessibilityEvent(event);
331a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
332a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        if (shouldSpeakPasswordsForAccessibility()) {
333a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            final CharSequence text = mText;
334a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            if (!TextUtils.isEmpty(text)) {
335a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos                event.getText().add(text);
336a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            }
337a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        }
338a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    }
339a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
340a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    @Override
341a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
342a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        super.onInitializeAccessibilityNodeInfo(info);
343a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
344a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        info.setClassName(PasswordTextView.class.getName());
345a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        info.setPassword(true);
346a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
347a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        if (shouldSpeakPasswordsForAccessibility()) {
348a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos            info.setText(mText);
349a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        }
350a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
351a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        info.setEditable(true);
352a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
353a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
354a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    }
355a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos
356a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    /**
357a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos     * @return true if the user has explicitly allowed accessibility services
358a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos     * to speak passwords.
359a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos     */
360a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos    private boolean shouldSpeakPasswordsForAccessibility() {
361a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos        return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
362a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos                Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
363a48caee9a717afe80a2210e5db3a4d6dabd952b5Adrian Roos                UserHandle.USER_CURRENT_OR_SELF) == 1);
3644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
3654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    private class CharState {
3674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        char whichChar;
3684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        ValueAnimator textAnimator;
3694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        boolean textAnimationIsGrowing;
3704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator dotAnimator;
3714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        boolean dotAnimationIsGrowing;
3724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        ValueAnimator widthAnimator;
3734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        boolean widthAnimationIsGrowing;
3744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentTextSizeFactor;
3754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentDotSizeFactor;
3764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentWidthFactor;
3774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        boolean isDotSwapPending;
3784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        float currentTextTranslationY = 1.0f;
3794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        ValueAnimator textTranslateAnimator;
3804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener removeEndListener = new AnimatorListenerAdapter() {
3824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            private boolean mCancelled;
3834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationCancel(Animator animation) {
3854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                mCancelled = true;
3864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
3894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
3904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                if (!mCancelled) {
3914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    mTextChars.remove(CharState.this);
3924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    mCharPool.push(CharState.this);
3934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    reset();
3944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    cancelAnimator(textTranslateAnimator);
3954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    textTranslateAnimator = null;
3964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                }
3974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
3984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
3994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationStart(Animator animation) {
4014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                mCancelled = false;
4024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener dotFinishListener = new AnimatorListenerAdapter() {
4064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
4084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                dotAnimator = null;
4094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener textFinishListener = new AnimatorListenerAdapter() {
4134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
4154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textAnimator = null;
4164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener textTranslateFinishListener = new AnimatorListenerAdapter() {
4204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
4224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator = null;
4234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        Animator.AnimatorListener widthFinishListener = new AnimatorListenerAdapter() {
4274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationEnd(Animator animation) {
4294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                widthAnimator = null;
4304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private ValueAnimator.AnimatorUpdateListener dotSizeUpdater
4344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                = new ValueAnimator.AnimatorUpdateListener() {
4354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
4374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                currentDotSizeFactor = (float) animation.getAnimatedValue();
4384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                invalidate();
4394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private ValueAnimator.AnimatorUpdateListener textSizeUpdater
4434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                = new ValueAnimator.AnimatorUpdateListener() {
4444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
4464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                currentTextSizeFactor = (float) animation.getAnimatedValue();
4474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                invalidate();
4484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private ValueAnimator.AnimatorUpdateListener textTranslationUpdater
4524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                = new ValueAnimator.AnimatorUpdateListener() {
4534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
4554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                currentTextTranslationY = (float) animation.getAnimatedValue();
4564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                invalidate();
4574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private ValueAnimator.AnimatorUpdateListener widthUpdater
4614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                = new ValueAnimator.AnimatorUpdateListener() {
4624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
4644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                currentWidthFactor = (float) animation.getAnimatedValue();
4654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                invalidate();
4664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private Runnable dotSwapperRunnable = new Runnable() {
4704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            @Override
4714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            public void run() {
4724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                performSwap();
4734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                isDotSwapPending = false;
4744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
4754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        };
4764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        void reset() {
4784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            whichChar = 0;
4794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentTextSizeFactor = 0.0f;
4804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentDotSizeFactor = 0.0f;
4814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentWidthFactor = 0.0f;
4824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(textAnimator);
4834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator = null;
4844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(dotAnimator);
4854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            dotAnimator = null;
4864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(widthAnimator);
4874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator = null;
4884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            currentTextTranslationY = 1.0f;
4894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            removeDotSwapCallbacks();
4904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
4914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
4924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        void startRemoveAnimation(long startDelay, long widthDelay) {
4934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null)
4944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    || (dotAnimator != null && dotAnimationIsGrowing);
4954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean textNeedsAnimation = (currentTextSizeFactor > 0.0f && textAnimator == null)
4964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    || (textAnimator != null && textAnimationIsGrowing);
4974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean widthNeedsAnimation = (currentWidthFactor > 0.0f && widthAnimator == null)
4984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    || (widthAnimator != null && widthAnimationIsGrowing);
4994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (dotNeedsAnimation) {
5004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startDotDisappearAnimation(startDelay);
5014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textNeedsAnimation) {
5034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startTextDisappearAnimation(startDelay);
5044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (widthNeedsAnimation) {
5064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startWidthDisappearAnimation(widthDelay);
5074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        void startAppearAnimation() {
5114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean dotNeedsAnimation = !mShowPassword
5124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    && (dotAnimator == null || !dotAnimationIsGrowing);
5134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean textNeedsAnimation = mShowPassword
5144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    && (textAnimator == null || !textAnimationIsGrowing);
5154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean widthNeedsAnimation = (widthAnimator == null || !widthAnimationIsGrowing);
5164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (dotNeedsAnimation) {
5174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startDotAppearAnimation(0);
5184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textNeedsAnimation) {
5204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startTextAppearAnimation();
5214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (widthNeedsAnimation) {
5234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                startWidthAppearAnimation();
5244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (mShowPassword) {
5264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                postDotSwap(TEXT_VISIBILITY_DURATION);
5274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        /**
5314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         * Posts a runnable which ensures that the text will be replaced by a dot after {@link
5324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         * com.android.keyguard.PasswordTextView#TEXT_VISIBILITY_DURATION}.
5334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         */
5344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void postDotSwap(long delay) {
5354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            removeDotSwapCallbacks();
5364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            postDelayed(dotSwapperRunnable, delay);
5374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            isDotSwapPending = true;
5384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void removeDotSwapCallbacks() {
5414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            removeCallbacks(dotSwapperRunnable);
5424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            isDotSwapPending = false;
5434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        void swapToDotWhenAppearFinished() {
5464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            removeDotSwapCallbacks();
5474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textAnimator != null) {
5484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                long remainingDuration = textAnimator.getDuration()
5494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        - textAnimator.getCurrentPlayTime();
5504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                postDotSwap(remainingDuration + TEXT_REST_DURATION_AFTER_APPEAR);
5514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            } else {
5524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                performSwap();
5534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
5544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void performSwap() {
5574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            startTextDisappearAnimation(0);
5584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            startDotAppearAnimation(DISAPPEAR_DURATION
5594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                    - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
5604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startWidthDisappearAnimation(long widthDelay) {
5634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(widthAnimator);
5644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 0.0f);
5654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addUpdateListener(widthUpdater);
5664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addListener(widthFinishListener);
5674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addListener(removeEndListener);
5684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.setDuration((long) (DISAPPEAR_DURATION * currentWidthFactor));
5694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.setStartDelay(widthDelay);
5704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.start();
5714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimationIsGrowing = false;
5724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startTextDisappearAnimation(long startDelay) {
5754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(textAnimator);
5764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 0.0f);
5774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.addUpdateListener(textSizeUpdater);
5784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.addListener(textFinishListener);
5794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setInterpolator(mDisappearInterpolator);
5804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setDuration((long) (DISAPPEAR_DURATION * currentTextSizeFactor));
5814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setStartDelay(startDelay);
5824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.start();
5834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimationIsGrowing = false;
5844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
5864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startDotDisappearAnimation(long startDelay) {
5874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(dotAnimator);
5884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            ValueAnimator animator = ValueAnimator.ofFloat(currentDotSizeFactor, 0.0f);
5894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.addUpdateListener(dotSizeUpdater);
5904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.addListener(dotFinishListener);
5914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.setInterpolator(mDisappearInterpolator);
5924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            long duration = (long) (DISAPPEAR_DURATION * Math.min(currentDotSizeFactor, 1.0f));
5934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.setDuration(duration);
5944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.setStartDelay(startDelay);
5954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            animator.start();
5964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            dotAnimator = animator;
5974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            dotAnimationIsGrowing = false;
5984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
5994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
6004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startWidthAppearAnimation() {
6014e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(widthAnimator);
6024e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 1.0f);
6034e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addUpdateListener(widthUpdater);
6044e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.addListener(widthFinishListener);
6054e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentWidthFactor)));
6064e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimator.start();
6074e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            widthAnimationIsGrowing = true;
6084e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
6094e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
6104e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startTextAppearAnimation() {
6114e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(textAnimator);
6124e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 1.0f);
6134e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.addUpdateListener(textSizeUpdater);
6144e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.addListener(textFinishListener);
6154e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setInterpolator(mAppearInterpolator);
6164e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentTextSizeFactor)));
6174e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimator.start();
6184e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            textAnimationIsGrowing = true;
6194e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
6204e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            // handle translation
6214e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textTranslateAnimator == null) {
6224e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
6234e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.addUpdateListener(textTranslationUpdater);
6244e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.addListener(textTranslateFinishListener);
6254e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.setInterpolator(mAppearInterpolator);
6264e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.setDuration(APPEAR_DURATION);
6274e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                textTranslateAnimator.start();
6284e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
6294e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
6304e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
6314e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void startDotAppearAnimation(long delay) {
6324e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            cancelAnimator(dotAnimator);
6334e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (!mShowPassword) {
6344e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                // We perform an overshoot animation
6354e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                ValueAnimator overShootAnimator = ValueAnimator.ofFloat(currentDotSizeFactor,
6364e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        DOT_OVERSHOOT_FACTOR);
6374e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                overShootAnimator.addUpdateListener(dotSizeUpdater);
6384e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                overShootAnimator.setInterpolator(mAppearInterpolator);
6394e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                long overShootDuration = (long) (DOT_APPEAR_DURATION_OVERSHOOT
6404e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        * OVERSHOOT_TIME_POSITION);
6414e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                overShootAnimator.setDuration(overShootDuration);
6424e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                ValueAnimator settleBackAnimator = ValueAnimator.ofFloat(DOT_OVERSHOOT_FACTOR,
6434e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        1.0f);
6444e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                settleBackAnimator.addUpdateListener(dotSizeUpdater);
6454e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                settleBackAnimator.setDuration(DOT_APPEAR_DURATION_OVERSHOOT - overShootDuration);
6464e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                settleBackAnimator.addListener(dotFinishListener);
6474e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                AnimatorSet animatorSet = new AnimatorSet();
6484e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                animatorSet.playSequentially(overShootAnimator, settleBackAnimator);
6494e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                animatorSet.setStartDelay(delay);
6504e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                animatorSet.start();
6514e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                dotAnimator = animatorSet;
6524e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            } else {
6534e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                ValueAnimator growAnimator = ValueAnimator.ofFloat(currentDotSizeFactor, 1.0f);
6544e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.addUpdateListener(dotSizeUpdater);
6554e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.setDuration((long) (APPEAR_DURATION * (1.0f - currentDotSizeFactor)));
6564e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.addListener(dotFinishListener);
6574e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.setStartDelay(delay);
6584e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                growAnimator.start();
6594e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                dotAnimator = growAnimator;
6604e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
6614e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            dotAnimationIsGrowing = true;
6624e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
6634e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
6644e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        private void cancelAnimator(Animator animator) {
6654e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (animator != null) {
6664e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                animator.cancel();
6674e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
6684e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
6694e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek
6704e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        /**
6714e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         * Draw this char to the canvas.
6724e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         *
6734e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         * @return The width this character contributes, including padding.
6744e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek         */
6754e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        public float draw(Canvas canvas, float currentDrawPosition, int charHeight, float yPosition,
6764e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                float charLength) {
6774e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean textVisible = currentTextSizeFactor > 0;
6784e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            boolean dotVisible = currentDotSizeFactor > 0;
6794e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            float charWidth = charLength * currentWidthFactor;
6804e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (textVisible) {
6814e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                float currYPosition = yPosition + charHeight / 2.0f * currentTextSizeFactor
6824e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                        + charHeight * currentTextTranslationY * 0.8f;
6834e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.save();
6844e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                float centerX = currentDrawPosition + charWidth / 2;
6854e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.translate(centerX, currYPosition);
6864e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.scale(currentTextSizeFactor, currentTextSizeFactor);
6874e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.drawText(Character.toString(whichChar), 0, 0, mDrawPaint);
6884e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.restore();
6894e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
6904e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            if (dotVisible) {
6914e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.save();
6924e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                float centerX = currentDrawPosition + charWidth / 2;
6934e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.translate(centerX, yPosition);
6944e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.drawCircle(0, 0, mDotSize / 2 * currentDotSizeFactor, mDrawPaint);
6954e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek                canvas.restore();
6964e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            }
6974e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek            return charWidth + mCharPadding * currentWidthFactor;
6984e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek        }
6994e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek    }
7004e8b9ed30b67e5449d987e674b2966dc7f3ac224Selim Cinek}
701