diff --git a/src/com/android/settings/fingerprint/FingerprintRemoveSidecar.java b/src/com/android/settings/fingerprint/FingerprintRemoveSidecar.java new file mode 100644 index 00000000000..eda4c82da52 --- /dev/null +++ b/src/com/android/settings/fingerprint/FingerprintRemoveSidecar.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 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.fingerprint; + +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Bundle; +import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import android.os.UserHandle; +import java.util.Queue; +import java.util.LinkedList; +import android.util.Log; + +/** + * Sidecar fragment to handle the state around fingerprint removal. + */ +public class FingerprintRemoveSidecar extends InstrumentedPreferenceFragment { + + private static final String TAG = "FingerprintRemoveSidecar"; + private Listener mListener; + private Fingerprint mFingerprintRemoving; + private Queue mFingerprintsRemoved; + FingerprintManager mFingerprintManager; + + private class RemovalError { + Fingerprint fingerprint; + int errMsgId; + CharSequence errString; + public RemovalError(Fingerprint fingerprint, int errMsgId, CharSequence errString) { + this.fingerprint = fingerprint; + this.errMsgId = errMsgId; + this.errString = errString; + } + } + + private FingerprintManager.RemovalCallback + mRemoveCallback = new FingerprintManager.RemovalCallback() { + @Override + public void onRemovalSucceeded(Fingerprint fingerprint) { + if (mListener != null) { + mListener.onRemovalSucceeded(fingerprint); + } else { + mFingerprintsRemoved.add(fingerprint); + }; + mFingerprintRemoving = null; + } + + @Override + public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { + if (mListener != null) { + mListener.onRemovalError(fp, errMsgId, errString); + } else { + mFingerprintsRemoved.add(new RemovalError(fp, errMsgId, errString)); + } + mFingerprintRemoving = null; + } + }; + + public void startRemove(Fingerprint fingerprint, int userId) { + if (mFingerprintRemoving != null) { + Log.e(TAG, "Remove already in progress"); + return; + } + if (userId != UserHandle.USER_NULL) { + mFingerprintManager.setActiveUser(userId); + } + mFingerprintRemoving = fingerprint; + mFingerprintManager.remove(fingerprint, userId, mRemoveCallback);; + } + + public FingerprintRemoveSidecar() { + mFingerprintsRemoved = new LinkedList<>(); + } + + public void setFingerprintManager(FingerprintManager fingerprintManager) { + mFingerprintManager = fingerprintManager; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + } + + public void setListener(Listener listener) { + if (mListener == null && listener != null) { + while (!mFingerprintsRemoved.isEmpty()) { + Object o = mFingerprintsRemoved.poll(); + if (o instanceof Fingerprint) { + listener.onRemovalSucceeded((Fingerprint)o); + } else if (o instanceof RemovalError) { + RemovalError e = (RemovalError) o; + listener.onRemovalError(e.fingerprint, e.errMsgId, e.errString); + } + } + } + mListener = listener; + } + + public interface Listener { + void onRemovalSucceeded(Fingerprint fingerprint); + void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString); + } + + final boolean isRemovingFingerprint(int fid) { + return inProgress() && mFingerprintRemoving.getFingerId() == fid; + } + + final boolean inProgress() { + return mFingerprintRemoving != null; + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.FINGERPRINT_REMOVE_SIDECAR; + } + +} diff --git a/src/com/android/settings/fingerprint/FingerprintSettings.java b/src/com/android/settings/fingerprint/FingerprintSettings.java index dc6616a2ce1..01670fe6579 100644 --- a/src/com/android/settings/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/fingerprint/FingerprintSettings.java @@ -31,7 +31,6 @@ import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; -import android.hardware.fingerprint.FingerprintManager.RemovalCallback; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; @@ -64,11 +63,11 @@ import com.android.settings.SubSettings; import com.android.settings.Utils; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.widget.FooterPreference; -import com.android.settings.widget.FooterPreferenceMixin; import com.android.settingslib.HelpUtils; import com.android.settingslib.RestrictedLockUtils; import java.util.List; +import java.util.HashMap; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -160,6 +159,10 @@ public class FingerprintSettings extends SubSettings { private Drawable mHighlightDrawable; private int mUserId; + private static final String TAG_REMOVAL_SIDECAR = "removal_sidecar"; + private FingerprintRemoveSidecar mRemovalSidecar; + private HashMap mFingerprintsRenaming; + private AuthenticationCallback mAuthCallback = new AuthenticationCallback() { @Override public void onAuthenticationSucceeded(AuthenticationResult result) { @@ -184,22 +187,30 @@ public class FingerprintSettings extends SubSettings { .sendToTarget(); } }; - private RemovalCallback mRemoveCallback = new RemovalCallback() { - @Override + FingerprintRemoveSidecar.Listener mRemovalListener = + new FingerprintRemoveSidecar.Listener() { public void onRemovalSucceeded(Fingerprint fingerprint) { mHandler.obtainMessage(MSG_REFRESH_FINGERPRINT_TEMPLATES, fingerprint.getFingerId(), 0).sendToTarget(); + updateDialog(); } - - @Override public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { final Activity activity = getActivity(); if (activity != null) { Toast.makeText(activity, errString, Toast.LENGTH_SHORT); } + updateDialog(); + } + private void updateDialog() { + RenameDeleteDialog renameDeleteDialog = (RenameDeleteDialog)getFragmentManager(). + findFragmentByTag(RenameDeleteDialog.class.getName()); + if (renameDeleteDialog != null) { + renameDeleteDialog.enableDelete(); + } } }; + private final Handler mHandler = new Handler() { @Override public void handleMessage(android.os.Message msg) { @@ -263,6 +274,9 @@ public class FingerprintSettings extends SubSettings { } private void retryFingerprint() { + if (mRemovalSidecar.inProgress()) { + return; + } if (!mInFingerprintLockout) { mFingerprintCancel = new CancellationSignal(); mFingerprintManager.authenticate(null, mFingerprintCancel, 0 /* flags */, @@ -278,7 +292,31 @@ public class FingerprintSettings extends SubSettings { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + Activity activity = getActivity(); + mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); + + mRemovalSidecar = (FingerprintRemoveSidecar) + getFragmentManager().findFragmentByTag(TAG_REMOVAL_SIDECAR); + if (mRemovalSidecar == null) { + mRemovalSidecar = new FingerprintRemoveSidecar(); + getFragmentManager().beginTransaction() + .add(mRemovalSidecar, TAG_REMOVAL_SIDECAR).commit(); + } + mRemovalSidecar.setFingerprintManager(mFingerprintManager); + mRemovalSidecar.setListener(mRemovalListener); + + RenameDeleteDialog renameDeleteDialog = (RenameDeleteDialog)getFragmentManager(). + findFragmentByTag(RenameDeleteDialog.class.getName()); + if (renameDeleteDialog != null) { + renameDeleteDialog.setDeleteInProgress(mRemovalSidecar.inProgress()); + } + + mFingerprintsRenaming = new HashMap(); + if (savedInstanceState != null) { + mFingerprintsRenaming = (HashMap) + savedInstanceState.getSerializable("mFingerprintsRenaming"); mToken = savedInstanceState.getByteArray( ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); mLaunchedConfirm = savedInstanceState.getBoolean( @@ -287,9 +325,6 @@ public class FingerprintSettings extends SubSettings { mUserId = getActivity().getIntent().getIntExtra( Intent.EXTRA_USER_ID, UserHandle.myUserId()); - Activity activity = getActivity(); - mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); - // Need to authenticate a session token if none if (mToken == null && mLaunchedConfirm == false) { mLaunchedConfirm = true; @@ -347,6 +382,12 @@ public class FingerprintSettings extends SubSettings { pref.setFingerprint(item); pref.setPersistent(false); pref.setIcon(R.drawable.ic_fingerprint_24dp); + if (mRemovalSidecar.isRemovingFingerprint(item.getFingerId())) { + pref.setEnabled(false); + } + if (mFingerprintsRenaming.containsKey(item.getFingerId())) { + pref.setTitle(mFingerprintsRenaming.get(item.getFingerId())); + } root.addPreference(pref); pref.setOnPreferenceChangeListener(this); } @@ -366,11 +407,14 @@ public class FingerprintSettings extends SubSettings { final int max = getContext().getResources().getInteger( com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser); boolean tooMany = mFingerprintManager.getEnrolledFingerprints(mUserId).size() >= max; + // retryFingerprint() will be called when remove finishes + // need to disable enroll or have a way to determine if enroll is in progress + final boolean removalInProgress = mRemovalSidecar.inProgress(); CharSequence maxSummary = tooMany ? getContext().getString(R.string.fingerprint_add_max, max) : ""; Preference addPreference = findPreference(KEY_FINGERPRINT_ADD); addPreference.setSummary(maxSummary); - addPreference.setEnabled(!tooMany); + addPreference.setEnabled(!tooMany && !removalInProgress); } private static String genKey(int id) { @@ -383,6 +427,9 @@ public class FingerprintSettings extends SubSettings { // Make sure we reload the preference hierarchy since fingerprints may be added, // deleted or renamed. updatePreferences(); + if (mRemovalSidecar != null) { + mRemovalSidecar.setListener(mRemovalListener); + } } private void updatePreferences() { @@ -394,6 +441,9 @@ public class FingerprintSettings extends SubSettings { public void onPause() { super.onPause(); stopFingerprint(); + if (mRemovalSidecar != null) { + mRemovalSidecar.setListener(null); + } } @Override @@ -401,6 +451,7 @@ public class FingerprintSettings extends SubSettings { outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); outState.putBoolean(KEY_LAUNCHED_CONFIRM, mLaunchedConfirm); + outState.putSerializable("mFingerprintsRenaming", mFingerprintsRenaming); } @Override @@ -415,7 +466,7 @@ public class FingerprintSettings extends SubSettings { startActivityForResult(intent, ADD_FINGERPRINT_REQUEST); } else if (pref instanceof FingerprintPreference) { FingerprintPreference fpref = (FingerprintPreference) pref; - final Fingerprint fp =fpref.getFingerprint(); + final Fingerprint fp = fpref.getFingerprint(); showRenameDeleteDialog(fp); return super.onPreferenceTreeClick(pref); } @@ -425,7 +476,14 @@ public class FingerprintSettings extends SubSettings { private void showRenameDeleteDialog(final Fingerprint fp) { RenameDeleteDialog renameDeleteDialog = new RenameDeleteDialog(); Bundle args = new Bundle(); - args.putParcelable("fingerprint", fp); + if (mFingerprintsRenaming.containsKey(fp.getFingerId())) { + final Fingerprint f = new Fingerprint(mFingerprintsRenaming.get(fp.getFingerId()), + fp.getGroupId(), fp.getFingerId(), fp.getDeviceId()); + args.putParcelable("fingerprint", f); + } else { + args.putParcelable("fingerprint", fp); + } + renameDeleteDialog.setDeleteInProgress(mRemovalSidecar.inProgress()); renameDeleteDialog.setArguments(args); renameDeleteDialog.setTargetFragment(this, 0); renameDeleteDialog.show(getFragmentManager(), RenameDeleteDialog.class.getName()); @@ -537,11 +595,16 @@ public class FingerprintSettings extends SubSettings { } private void deleteFingerPrint(Fingerprint fingerPrint) { - mFingerprintManager.remove(fingerPrint, mUserId, mRemoveCallback); + mRemovalSidecar.startRemove(fingerPrint, mUserId); + String name = genKey(fingerPrint.getFingerId()); + Preference prefToRemove = findPreference(name); + prefToRemove.setEnabled(false); + updateAddPreference(); } private void renameFingerPrint(int fingerId, String newName) { mFingerprintManager.rename(fingerId, mUserId, newName); + mFingerprintsRenaming.put(fingerId, newName); updatePreferences(); } @@ -561,7 +624,12 @@ public class FingerprintSettings extends SubSettings { private Boolean mTextHadFocus; private int mTextSelectionStart; private int mTextSelectionEnd; + private AlertDialog mAlertDialog; + private boolean mDeleteInProgress; + public void setDeleteInProgress(boolean deleteInProgress) { + mDeleteInProgress = deleteInProgress; + } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { mFp = getArguments().getParcelable("fingerprint"); @@ -571,7 +639,7 @@ public class FingerprintSettings extends SubSettings { mTextSelectionStart = savedInstanceState.getInt("startSelection"); mTextSelectionEnd = savedInstanceState.getInt("endSelection"); } - final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) + mAlertDialog = new AlertDialog.Builder(getActivity()) .setView(R.layout.fingerprint_rename_dialog) .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, new DialogInterface.OnClickListener() { @@ -605,10 +673,10 @@ public class FingerprintSettings extends SubSettings { } }) .create(); - alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { + mAlertDialog.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(DialogInterface dialog) { - mDialogTextField = (EditText) alertDialog.findViewById( + mDialogTextField = (EditText) mAlertDialog.findViewById( R.id.fingerprint_rename_field); CharSequence name = mFingerName == null ? mFp.getName() : mFingerName; mDialogTextField.setText(name); @@ -617,14 +685,24 @@ public class FingerprintSettings extends SubSettings { } else { mDialogTextField.setSelection(mTextSelectionStart, mTextSelectionEnd); } + if (mDeleteInProgress) { + mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false); + } } }); if (mTextHadFocus == null || mTextHadFocus) { // Request the IME - alertDialog.getWindow().setSoftInputMode( + mAlertDialog.getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } - return alertDialog; + return mAlertDialog; + } + + public void enableDelete() { + mDeleteInProgress = false; + if (mAlertDialog != null) { + mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(true); + } } private void onDeleteClick(DialogInterface dialog) {