| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings; |
| |
| import android.animation.Animator; |
| import android.animation.Animator.AnimatorListener; |
| import android.animation.ObjectAnimator; |
| import android.app.Activity; |
| import android.app.Fragment; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.graphics.drawable.AnimationDrawable; |
| import android.media.AudioAttributes; |
| import android.os.Bundle; |
| import android.os.PowerManager; |
| import android.os.SystemClock; |
| import android.os.Vibrator; |
| import android.service.fingerprint.FingerprintManager; |
| import android.service.fingerprint.FingerprintManagerReceiver; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.widget.ImageView; |
| import android.widget.ProgressBar; |
| import android.widget.TextView; |
| |
| import com.android.internal.widget.LockPatternUtils; |
| |
| /** |
| * Wizard to enroll a fingerprint |
| */ |
| public class FingerprintEnroll extends SettingsActivity { |
| /** |
| * Used by the choose fingerprint wizard to indicate the wizard is |
| * finished, and each activity in the wizard should finish. |
| * <p> |
| * Previously, each activity in the wizard would finish itself after |
| * starting the next activity. However, this leads to broken 'Back' |
| * behavior. So, now an activity does not finish itself until it gets this |
| * result. |
| */ |
| static final int RESULT_FINISHED = RESULT_FIRST_USER; |
| |
| @Override |
| public Intent getIntent() { |
| Intent modIntent = new Intent(super.getIntent()); |
| modIntent.putExtra(EXTRA_SHOW_FRAGMENT, FingerprintEnrollFragment.class.getName()); |
| return modIntent; |
| } |
| |
| @Override |
| protected boolean isValidFragment(String fragmentName) { |
| if (FingerprintEnrollFragment.class.getName().equals(fragmentName)) return true; |
| return false; |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| requestWindowFeature(Window.FEATURE_NO_TITLE); |
| super.onCreate(savedInstanceState); |
| CharSequence msg = getText(R.string.security_settings_fingerprint_preference_title); |
| setTitle(msg); |
| } |
| |
| public static class FingerprintEnrollFragment extends Fragment implements View.OnClickListener { |
| private static final String TAG = "FingerprintEnroll"; |
| private static final boolean DEBUG = true; |
| private static final int CONFIRM_REQUEST = 101; |
| private static final int CHOOSE_LOCK_GENERIC_REQUEST = 102; |
| private static final long ENROLL_TIMEOUT = 300*1000; |
| |
| private PowerManager mPowerManager; |
| private FingerprintManager mFingerprintManager; |
| private View mContentView; |
| private TextView mTitleText; |
| private TextView mMessageText; |
| private Stage mStage; |
| private int mEnrollmentSteps; |
| private boolean mEnrolling; |
| private Vibrator mVibrator; |
| private ProgressBar mProgressBar; |
| private ImageView mFingerprintAnimator; |
| private ObjectAnimator mProgressAnim; |
| private final AnimatorListener mProgressAnimationListener = new AnimatorListener() { |
| @Override |
| public void onAnimationStart(Animator animation) { } |
| |
| @Override |
| public void onAnimationRepeat(Animator animation) { } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| if (mProgressBar.getProgress() >= 100) { |
| updateStage(Stage.EnrollingFinish); |
| } |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { } |
| }; |
| |
| // This contains a list of all views managed by the UI. Used to determine which views |
| // need to be shown/hidden at each stage. It should be the union of the lists that follow |
| private static final int MANAGED_VIEWS[] = { |
| R.id.fingerprint_sensor_location, |
| R.id.fingerprint_animator, |
| R.id.fingerprint_enroll_button_area, |
| R.id.fingerprint_in_app_indicator, |
| R.id.fingerprint_enroll_button_add, |
| R.id.fingerprint_enroll_button_next, |
| R.id.fingerprint_progress_bar |
| }; |
| |
| private static final int VIEWS_ENROLL_ONBOARD[] = { |
| R.id.fingerprint_enroll_button_area, |
| R.id.fingerprint_enroll_button_next |
| }; |
| |
| private static final int VIEWS_ENROLL_START[] = { |
| R.id.fingerprint_sensor_location, |
| R.id.fingerprint_progress_bar |
| }; |
| |
| private static final int VIEWS_ENROLL_PROGRESS[] = { |
| R.id.fingerprint_animator, |
| R.id.fingerprint_progress_bar |
| }; |
| |
| private static final int VIEWS_ENROLL_FINISH[] = { |
| R.id.fingerprint_enroll_button_area, |
| R.id.fingerprint_in_app_indicator, |
| R.id.fingerprint_enroll_button_add, |
| R.id.fingerprint_enroll_button_next |
| }; |
| |
| enum Stage { |
| EnrollingOnboarding(R.string.security_settings_fingerprint_enroll_onboard_title, |
| R.string.security_settings_fingerprint_enroll_onboard_message, |
| VIEWS_ENROLL_ONBOARD), |
| EnrollingStart(R.string.security_settings_fingerprint_enroll_start_title, |
| R.string.security_settings_fingerprint_enroll_start_message, |
| VIEWS_ENROLL_START), |
| EnrollingRepeat(R.string.security_settings_fingerprint_enroll_repeat_title, |
| R.string.security_settings_fingerprint_enroll_repeat_message, |
| VIEWS_ENROLL_PROGRESS), |
| EnrollingFinish(R.string.security_settings_fingerprint_enroll_finish_title, |
| R.string.security_settings_fingerprint_enroll_finish_message, |
| VIEWS_ENROLL_FINISH); |
| |
| Stage(int title, int message, int[] enabledViewIds) { |
| this.title = title; |
| this.message = message; |
| this.enabledViewIds = enabledViewIds; |
| } |
| |
| public int title; |
| public int message; |
| public int[] enabledViewIds; |
| }; |
| |
| void updateStage(Stage stage) { |
| if (DEBUG) Log.v(TAG, "updateStage(" + stage.toString() + ")"); |
| |
| // Show/hide views |
| for (int i = 0; i < MANAGED_VIEWS.length; i++) { |
| mContentView.findViewById(MANAGED_VIEWS[i]).setVisibility(View.INVISIBLE); |
| } |
| for (int i = 0; i < stage.enabledViewIds.length; i++) { |
| mContentView.findViewById(stage.enabledViewIds[i]).setVisibility(View.VISIBLE); |
| } |
| |
| setTitleMessage(stage.title); |
| setMessage(stage.message); |
| |
| if (mStage != stage) { |
| onStageChanged(stage); |
| mStage = stage; |
| } |
| } |
| |
| private void startFingerprintAnimator() { |
| AnimationDrawable drawable = (AnimationDrawable) mFingerprintAnimator.getDrawable(); |
| drawable.start(); |
| } |
| |
| private void stopFingerprintAnimator() { |
| AnimationDrawable drawable = (AnimationDrawable) mFingerprintAnimator.getDrawable(); |
| drawable.stop(); |
| drawable.setLevel(0); |
| } |
| |
| private void onStageChanged(Stage stage) { |
| // Update state |
| switch (stage) { |
| case EnrollingOnboarding: |
| mEnrollmentSteps = -1; |
| mEnrolling = false; |
| break; |
| |
| case EnrollingStart: |
| mEnrollmentSteps = -1; |
| mFingerprintManager.startListening(mReceiver); |
| mFingerprintManager.enroll(ENROLL_TIMEOUT); |
| mProgressBar.setProgress(0); |
| mEnrolling = true; |
| startFingerprintAnimator(); // XXX hack - this should follow fingerprint detection |
| break; |
| |
| case EnrollingRepeat: |
| break; |
| |
| case EnrollingFinish: |
| stopFingerprintAnimator(); // XXX hack - this should follow fingerprint detection |
| mFingerprintManager.stopListening(); |
| mEnrolling = false; |
| break; |
| |
| default: |
| mFingerprintManager.stopListening(); |
| break; |
| } |
| } |
| |
| private void cancelEnrollment() { |
| if (mEnrolling) { |
| if (DEBUG) Log.v(TAG, "Cancel enrollment\n"); |
| mFingerprintManager.enrollCancel(); |
| mEnrolling = false; |
| } |
| } |
| |
| @Override |
| public void onDetach() { |
| super.onDetach(); |
| // Do a little cleanup |
| cancelEnrollment(); |
| mFingerprintManager.stopListening(); |
| } |
| |
| private void updateProgress(int progress) { |
| if (DEBUG) Log.v(TAG, "Progress: " + progress); |
| if (mVibrator != null) { |
| mVibrator.vibrate(100, new AudioAttributes.Builder() |
| .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) |
| .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()); |
| } |
| if (mProgressAnim != null) { |
| mProgressAnim.cancel(); |
| } |
| ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress", |
| mProgressBar.getProgress(), progress); |
| anim.addListener(mProgressAnimationListener); |
| anim.start(); |
| mProgressAnim = anim; |
| } |
| |
| private void setMessage(int id) { |
| if (id != 0) mMessageText.setText(id); |
| } |
| |
| private void setTitleMessage(int title) { |
| if (title != 0) mTitleText.setText(title); |
| } |
| |
| private FingerprintManagerReceiver mReceiver = new FingerprintManagerReceiver() { |
| public void onEnrollResult(int fingerprintId, int remaining) { |
| if (DEBUG) Log.v(TAG, "onEnrollResult(id=" + fingerprintId + ", rem=" + remaining); |
| if (mEnrollmentSteps == -1) { |
| mEnrollmentSteps = remaining; |
| updateStage(Stage.EnrollingRepeat); |
| } |
| if (remaining >= 0) { |
| int progress = Math.max(0, mEnrollmentSteps + 1 - remaining); |
| updateProgress(100*progress / (mEnrollmentSteps + 1)); |
| // Treat fingerprint like a touch event |
| mPowerManager.userActivity(SystemClock.uptimeMillis(), |
| PowerManager.USER_ACTIVITY_EVENT_OTHER, |
| PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS); |
| } |
| } |
| |
| public void onError(int error) { |
| switch(error) { |
| case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS: |
| setMessage(R.string.fingerprint_error_unable_to_process); |
| break; |
| case FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE: |
| setMessage(R.string.fingerprint_error_hw_not_available); |
| break; |
| case FingerprintManager.FINGERPRINT_ERROR_NO_SPACE: |
| setMessage(R.string.fingerprint_error_no_space); |
| break; |
| case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT: |
| setMessage(R.string.fingerprint_error_timeout); |
| break; |
| case FingerprintManager.FINGERPRINT_ERROR_NO_RECEIVER: |
| Log.w(TAG, "Receiver not registered"); |
| break; |
| } |
| } |
| |
| public void onRemoved(int fingerprintId) { |
| if (DEBUG) Log.v(TAG, "onRemoved(id=" + fingerprintId + ")"); |
| } |
| |
| @Override |
| public void onProcessed(int fingerprintId) { |
| if (DEBUG) Log.v(TAG, "onProcessed(id=" + fingerprintId + ")"); |
| } |
| |
| public void onAcquired(int scanInfo) { |
| int msgId = 0; |
| startFingerprintAnimator(); |
| switch(scanInfo) { |
| case FingerprintManager.FINGERPRINT_ACQUIRED_GOOD: |
| break; |
| case FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY: |
| msgId = R.string.fingerprint_acquired_imager_dirty; |
| break; |
| case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_SLOW: |
| msgId = R.string.fingerprint_acquired_too_fast; |
| break; |
| case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST: |
| msgId = R.string.fingerprint_acquired_too_slow; |
| break; |
| case FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL: |
| case FingerprintManager.FINGERPRINT_ACQUIRED_INSUFFICIENT: |
| msgId = R.string.fingerprint_acquired_try_again; |
| break; |
| default: |
| // Try not to be too verbose in the UI. The user just needs to try again. |
| // Log the message so we can dig into the issue if necessary. |
| Log.w(TAG, "Try again because scanInfo was " + scanInfo); |
| msgId = R.string.fingerprint_acquired_try_again; |
| break; |
| } |
| setMessage(msgId); |
| } |
| }; |
| |
| private boolean runConfirmDeviceCredentials(int request) { |
| if (DEBUG) Log.v(TAG, "runKeyguardConfirmation(" + request + ")"); |
| Resources res = getResources(); |
| return new ChooseLockSettingsHelper(getActivity(), this) |
| .launchConfirmationActivity(request, |
| res.getText(R.string.master_clear_gesture_prompt), |
| res.getText(R.string.master_clear_gesture_explanation)); |
| } |
| |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| super.onActivityResult(requestCode, resultCode, data); |
| |
| if (requestCode == CHOOSE_LOCK_GENERIC_REQUEST) { |
| if (resultCode == RESULT_FINISHED) { |
| // The lock pin/pattern/password was set. Start enrolling! |
| updateStage(Stage.EnrollingStart); |
| } |
| } |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| final Activity activity = getActivity(); |
| mFingerprintManager = (FingerprintManager)activity |
| .getSystemService(Context.FINGERPRINT_SERVICE); |
| mVibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); |
| mPowerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE); |
| |
| mContentView = inflater.inflate(R.layout.fingerprint_enroll, null); |
| mTitleText = (TextView) mContentView.findViewById(R.id.fingerprint_enroll_title); |
| mMessageText = (TextView) mContentView.findViewById(R.id.fingerprint_enroll_message); |
| mProgressBar = (ProgressBar) mContentView.findViewById(R.id.fingerprint_progress_bar); |
| mFingerprintAnimator = (ImageView) mContentView.findViewById(R.id.fingerprint_animator); |
| |
| final int buttons[] = { |
| R.id.fingerprint_enroll_button_add, |
| R.id.fingerprint_enroll_button_next }; |
| for (int i = 0; i < buttons.length; i++) { |
| mContentView.findViewById(buttons[i]).setOnClickListener(this); |
| } |
| |
| LockPatternUtils utils = new LockPatternUtils(activity); |
| if (!utils.isSecure()) { |
| // Device doesn't have any security. Set that up first. |
| updateStage(Stage.EnrollingOnboarding); |
| } else { |
| updateStage(Stage.EnrollingStart); |
| } |
| return mContentView; |
| } |
| |
| @Override |
| public void onClick(View v) { |
| switch(v.getId()) { |
| case R.id.fingerprint_enroll_button_add: |
| updateStage(Stage.EnrollingStart); |
| break; |
| case R.id.fingerprint_enroll_button_next: |
| if (mStage == Stage.EnrollingOnboarding) { |
| launchChooseLock(); |
| } else if (mStage == Stage.EnrollingFinish) { |
| getActivity().finish(); |
| } else { |
| Log.v(TAG, "No idea what to do next!"); |
| } |
| break; |
| } |
| } |
| |
| private void launchChooseLock() { |
| Intent intent = new Intent(); |
| intent.setClassName("com.android.settings", ChooseLockGeneric.class.getName()); |
| startActivityForResult(intent, CHOOSE_LOCK_GENERIC_REQUEST); |
| } |
| } |
| } |