FingerprintEnrollEnrolling.java revision 0c0858acad490a615871e7d9c1d3b73450398041
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.settings.fingerprint;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.app.AlertDialog;
24import android.app.Dialog;
25import android.app.DialogFragment;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.res.ColorStateList;
29import android.graphics.drawable.Animatable2;
30import android.graphics.drawable.AnimatedVectorDrawable;
31import android.graphics.drawable.Drawable;
32import android.os.Bundle;
33import android.view.MotionEvent;
34import android.view.View;
35import android.view.animation.AnimationUtils;
36import android.view.animation.Interpolator;
37import android.widget.ImageView;
38import android.widget.ProgressBar;
39import android.widget.TextView;
40
41import com.android.settings.ChooseLockSettingsHelper;
42import com.android.settings.R;
43
44/**
45 * Activity which handles the actual enrolling for fingerprint.
46 */
47public class FingerprintEnrollEnrolling extends FingerprintEnrollBase
48        implements FingerprintEnrollSidecar.Listener {
49
50    private static final String TAG_SIDECAR = "sidecar";
51
52    private static final int PROGRESS_BAR_MAX = 10000;
53    private static final int FINISH_DELAY = 250;
54
55    /**
56     * If we don't see progress during this time, we show an error message to remind the user that
57     * he needs to lift the finger and touch again.
58     */
59    private static final int HINT_TIMEOUT_DURATION = 2500;
60
61    /**
62     * How long the user needs to touch the icon until we show the dialog.
63     */
64    private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500;
65
66    /**
67     * How many times the user needs to touch the icon until we show the dialog that this is not the
68     * fingerprint sensor.
69     */
70    private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3;
71
72    private ProgressBar mProgressBar;
73    private ImageView mFingerprintAnimator;
74    private ObjectAnimator mProgressAnim;
75    private TextView mStartMessage;
76    private TextView mRepeatMessage;
77    private TextView mErrorText;
78    private Interpolator mFastOutSlowInInterpolator;
79    private Interpolator mLinearOutSlowInInterpolator;
80    private Interpolator mFastOutLinearInInterpolator;
81    private int mIconTouchCount;
82    private FingerprintEnrollSidecar mSidecar;
83    private boolean mAnimationCancelled;
84    private AnimatedVectorDrawable mIconAnimationDrawable;
85    private int mIndicatorBackgroundRestingColor;
86    private int mIndicatorBackgroundActivatedColor;
87    private boolean mRestoring;
88
89    @Override
90    protected void onCreate(Bundle savedInstanceState) {
91        super.onCreate(savedInstanceState);
92        setContentView(R.layout.fingerprint_enroll_enrolling);
93        setHeaderText(R.string.security_settings_fingerprint_enroll_start_title);
94        mStartMessage = (TextView) findViewById(R.id.start_message);
95        mRepeatMessage = (TextView) findViewById(R.id.repeat_message);
96        mErrorText = (TextView) findViewById(R.id.error_text);
97        mProgressBar = (ProgressBar) findViewById(R.id.fingerprint_progress_bar);
98        mFingerprintAnimator = (ImageView) findViewById(R.id.fingerprint_animator);
99        mIconAnimationDrawable = (AnimatedVectorDrawable) mFingerprintAnimator.getDrawable();
100        mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback);
101        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
102                this, android.R.interpolator.fast_out_slow_in);
103        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
104                this, android.R.interpolator.linear_out_slow_in);
105        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
106                this, android.R.interpolator.fast_out_linear_in);
107        mFingerprintAnimator.setOnTouchListener(new View.OnTouchListener() {
108            @Override
109            public boolean onTouch(View v, MotionEvent event) {
110                if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
111                    mIconTouchCount++;
112                    if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) {
113                        showIconTouchDialog();
114                    } else {
115                        mFingerprintAnimator.postDelayed(mShowDialogRunnable,
116                                ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN);
117                    }
118                } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
119                        || event.getActionMasked() == MotionEvent.ACTION_UP) {
120                    mFingerprintAnimator.removeCallbacks(mShowDialogRunnable);
121                }
122                return true;
123            }
124        });
125        mIndicatorBackgroundRestingColor
126                = getColor(R.color.fingerprint_indicator_background_resting);
127        mIndicatorBackgroundActivatedColor
128                = getColor(R.color.fingerprint_indicator_background_activated);
129        mRestoring = savedInstanceState != null;
130    }
131
132    @Override
133    protected void onStart() {
134        super.onStart();
135        mSidecar = (FingerprintEnrollSidecar) getFragmentManager().findFragmentByTag(TAG_SIDECAR);
136        if (mSidecar == null) {
137            mSidecar = new FingerprintEnrollSidecar();
138            getFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR).commit();
139        }
140        mSidecar.setListener(this);
141        updateProgress(false /* animate */);
142        updateDescription();
143        if (mRestoring) {
144            startIconAnimation();
145        }
146    }
147
148    @Override
149    public void onEnterAnimationComplete() {
150        super.onEnterAnimationComplete();
151        mAnimationCancelled = false;
152        startIconAnimation();
153    }
154
155    private void startIconAnimation() {
156        mIconAnimationDrawable.start();
157    }
158
159    private void stopIconAnimation() {
160        mAnimationCancelled = true;
161        mIconAnimationDrawable.stop();
162    }
163
164    @Override
165    protected void onStop() {
166        super.onStop();
167        mSidecar.setListener(null);
168        stopIconAnimation();
169        if (!isChangingConfigurations()) {
170            finish();
171        }
172    }
173
174    private void animateProgress(int progress) {
175        if (mProgressAnim != null) {
176            mProgressAnim.cancel();
177        }
178        ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress",
179                mProgressBar.getProgress(), progress);
180        anim.addListener(mProgressAnimationListener);
181        anim.setInterpolator(mFastOutSlowInInterpolator);
182        anim.setDuration(250);
183        anim.start();
184        mProgressAnim = anim;
185    }
186
187    private void animateFlash() {
188        ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundRestingColor,
189                mIndicatorBackgroundActivatedColor);
190        final ValueAnimator.AnimatorUpdateListener listener =
191                new ValueAnimator.AnimatorUpdateListener() {
192            @Override
193            public void onAnimationUpdate(ValueAnimator animation) {
194                mFingerprintAnimator.setBackgroundTintList(ColorStateList.valueOf(
195                        (Integer) animation.getAnimatedValue()));
196            }
197        };
198        anim.addUpdateListener(listener);
199        anim.addListener(new AnimatorListenerAdapter() {
200            @Override
201            public void onAnimationEnd(Animator animation) {
202                ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundActivatedColor,
203                        mIndicatorBackgroundRestingColor);
204                anim.addUpdateListener(listener);
205                anim.setDuration(300);
206                anim.setInterpolator(mLinearOutSlowInInterpolator);
207                anim.start();
208            }
209        });
210        anim.setInterpolator(mFastOutSlowInInterpolator);
211        anim.setDuration(300);
212        anim.start();
213    }
214
215    private void launchFinish(byte[] token) {
216        Intent intent = new Intent();
217        intent.setClassName("com.android.settings", FingerprintEnrollFinish.class.getName());
218        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
219        startActivity(intent);
220        setResult(RESULT_FINISHED);
221        finish();
222    }
223
224    private void updateDescription() {
225        if (mSidecar.getEnrollmentSteps() == -1) {
226            setHeaderText(R.string.security_settings_fingerprint_enroll_start_title);
227            mStartMessage.setVisibility(View.VISIBLE);
228            mRepeatMessage.setVisibility(View.INVISIBLE);
229        } else {
230            setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title,
231                    true /* force */);
232            mStartMessage.setVisibility(View.INVISIBLE);
233            mRepeatMessage.setVisibility(View.VISIBLE);
234        }
235    }
236
237
238    @Override
239    public void onEnrollmentHelp(CharSequence helpString) {
240        mErrorText.setText(helpString);
241    }
242
243    @Override
244    public void onEnrollmentError(CharSequence errString) {
245        showError(errString);
246        stopIconAnimation();
247    }
248
249    @Override
250    public void onEnrollmentProgressChange(int steps, int remaining) {
251        updateProgress(true /* animate */);
252        updateDescription();
253        clearError();
254        animateFlash();
255        mErrorText.removeCallbacks(mTouchAgainRunnable);
256        mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION);
257    }
258
259    private void updateProgress(boolean animate) {
260        int progress = getProgress(
261                mSidecar.getEnrollmentSteps(), mSidecar.getEnrollmentRemaining());
262        if (animate) {
263            animateProgress(progress);
264        } else {
265            mProgressBar.setProgress(progress);
266        }
267    }
268
269    private int getProgress(int steps, int remaining) {
270        if (steps == -1) {
271            return 0;
272        }
273        int progress = Math.max(0, steps + 1 - remaining);
274        return PROGRESS_BAR_MAX * progress / (steps + 1);
275    }
276
277    private void showIconTouchDialog() {
278        mIconTouchCount = 0;
279        new IconTouchDialog().show(getFragmentManager(), null /* tag */);
280    }
281
282    private void showError(CharSequence error) {
283        mErrorText.setText(error);
284        if (mErrorText.getVisibility() == View.INVISIBLE) {
285            mErrorText.setVisibility(View.VISIBLE);
286            mErrorText.setTranslationY(getResources().getDimensionPixelSize(
287                    R.dimen.fingerprint_error_text_appear_distance));
288            mErrorText.setAlpha(0f);
289            mErrorText.animate()
290                    .alpha(1f)
291                    .translationY(0f)
292                    .setDuration(200)
293                    .setInterpolator(mLinearOutSlowInInterpolator)
294                    .start();
295        } else {
296            mErrorText.animate().cancel();
297            mErrorText.setAlpha(1f);
298            mErrorText.setTranslationY(0f);
299        }
300    }
301
302    private void clearError() {
303        if (mErrorText.getVisibility() == View.VISIBLE) {
304            mErrorText.animate()
305                    .alpha(0f)
306                    .translationY(getResources().getDimensionPixelSize(
307                            R.dimen.fingerprint_error_text_disappear_distance))
308                    .setDuration(100)
309                    .setInterpolator(mFastOutLinearInInterpolator)
310                    .withEndAction(new Runnable() {
311                        @Override
312                        public void run() {
313                            mErrorText.setVisibility(View.INVISIBLE);
314                        }
315                    })
316                    .start();
317        }
318    }
319
320    private final Animator.AnimatorListener mProgressAnimationListener
321            = new Animator.AnimatorListener() {
322
323        @Override
324        public void onAnimationStart(Animator animation) { }
325
326        @Override
327        public void onAnimationRepeat(Animator animation) { }
328
329        @Override
330        public void onAnimationEnd(Animator animation) {
331            if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) {
332                mProgressBar.postDelayed(mDelayedFinishRunnable, FINISH_DELAY);
333            }
334        }
335
336        @Override
337        public void onAnimationCancel(Animator animation) { }
338    };
339
340    // Give the user a chance to see progress completed before jumping to the next stage.
341    private final Runnable mDelayedFinishRunnable = new Runnable() {
342        @Override
343        public void run() {
344            launchFinish(mToken);
345        }
346    };
347
348    private final Animatable2.AnimationCallback mIconAnimationCallback =
349            new Animatable2.AnimationCallback() {
350        @Override
351        public void onAnimationEnd(Drawable d) {
352            if (mAnimationCancelled) {
353                return;
354            }
355
356            // Start animation after it has ended.
357            mFingerprintAnimator.post(new Runnable() {
358                @Override
359                public void run() {
360                    startIconAnimation();
361                }
362            });
363        }
364    };
365
366    private final Runnable mShowDialogRunnable = new Runnable() {
367        @Override
368        public void run() {
369            showIconTouchDialog();
370        }
371    };
372
373    private final Runnable mTouchAgainRunnable = new Runnable() {
374        @Override
375        public void run() {
376            showError(getString(R.string.security_settings_fingerprint_enroll_lift_touch_again));
377        }
378    };
379
380    public static class IconTouchDialog extends DialogFragment {
381
382        @Override
383        public Dialog onCreateDialog(Bundle savedInstanceState) {
384            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
385            builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
386                    .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
387                    .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
388                            new DialogInterface.OnClickListener() {
389                                @Override
390                                public void onClick(DialogInterface dialog, int which) {
391                                    dialog.dismiss();
392                                }
393                            });
394            return builder.create();
395        }
396    }
397}
398