diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4fa43839675..c582e6c4656 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -72,6 +72,8 @@
+ * 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);
+ }
+ }
+}
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index e809bb5b63c..95826e4e048 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -41,6 +41,8 @@ import android.provider.SearchIndexableResource;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.security.KeyStore;
+import android.service.fingerprint.FingerprintManager;
+import android.service.fingerprint.FingerprintManager.FingerprintItem;
import android.service.trust.TrustAgentService;
import android.telephony.TelephonyManager;
import android.telephony.SubscriptionManager;
@@ -220,32 +222,12 @@ public class SecuritySettings extends SettingsPreferenceFragment
}
}
- // Trust Agent preferences
+ // Fingerprint and trust agents
PreferenceGroup securityCategory = (PreferenceGroup)
root.findPreference(KEY_SECURITY_CATEGORY);
if (securityCategory != null) {
- final boolean hasSecurity = mLockPatternUtils.isSecure();
- ArrayList