diff --git a/res/layout/preference_widget_delete.xml b/res/layout/preference_widget_delete.xml
new file mode 100644
index 00000000000..b62946c3513
--- /dev/null
+++ b/res/layout/preference_widget_delete.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d4af6bfd50b..df7a08c6f65 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -920,6 +920,12 @@
Remove all fingerprints?
+
+ Remove \'%1$s\'
+
+
+ Do you want to delete this fingerprint?
+
You won\'t be able to use your fingerprints to
unlock your phone, authorize purchases, or sign in to apps with them
diff --git a/src/com/android/settings/fingerprint/FingerprintSettings.java b/src/com/android/settings/fingerprint/FingerprintSettings.java
index 89c3d78469f..2425c61dcce 100644
--- a/src/com/android/settings/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/fingerprint/FingerprintSettings.java
@@ -36,6 +36,7 @@ import android.os.CancellationSignal;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
@@ -48,7 +49,6 @@ import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.URLSpan;
-import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@@ -66,6 +66,7 @@ import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.settingslib.TwoTargetPreference;
import com.android.settingslib.widget.FooterPreference;
import java.util.HashMap;
@@ -127,7 +128,7 @@ public class FingerprintSettings extends SubSettings {
}
public static class FingerprintSettingsFragment extends SettingsPreferenceFragment
- implements OnPreferenceChangeListener {
+ implements OnPreferenceChangeListener, FingerprintPreference.OnDeleteClickListener {
private static final int MAX_RETRY_ATTEMPTS = 20;
private static final int RESET_HIGHLIGHT_DELAY_MS = 500;
@@ -173,7 +174,7 @@ public class FingerprintSettings extends SubSettings {
@Override
public void onAuthenticationFailed() {
mHandler.obtainMessage(MSG_FINGER_AUTH_FAIL).sendToTarget();
- };
+ }
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
@@ -195,6 +196,7 @@ public class FingerprintSettings extends SubSettings {
fingerprint.getFingerId(), 0).sendToTarget();
updateDialog();
}
+
public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) {
final Activity activity = getActivity();
if (activity != null) {
@@ -202,11 +204,12 @@ public class FingerprintSettings extends SubSettings {
}
updateDialog();
}
+
private void updateDialog() {
- RenameDeleteDialog renameDeleteDialog = (RenameDeleteDialog)getFragmentManager().
- findFragmentByTag(RenameDeleteDialog.class.getName());
- if (renameDeleteDialog != null) {
- renameDeleteDialog.enableDelete();
+ RenameDialog renameDialog = (RenameDialog) getFragmentManager().
+ findFragmentByTag(RenameDialog.class.getName());
+ if (renameDialog != null) {
+ renameDialog.enableDelete();
}
}
};
@@ -236,7 +239,7 @@ public class FingerprintSettings extends SubSettings {
}
break;
}
- };
+ }
};
private void stopFingerprint() {
@@ -310,10 +313,10 @@ public class FingerprintSettings extends SubSettings {
mRemovalSidecar.setFingerprintManager(mFingerprintManager);
mRemovalSidecar.setListener(mRemovalListener);
- RenameDeleteDialog renameDeleteDialog = (RenameDeleteDialog)getFragmentManager().
- findFragmentByTag(RenameDeleteDialog.class.getName());
- if (renameDeleteDialog != null) {
- renameDeleteDialog.setDeleteInProgress(mRemovalSidecar.inProgress());
+ RenameDialog renameDialog = (RenameDialog) getFragmentManager().
+ findFragmentByTag(RenameDialog.class.getName());
+ if (renameDialog != null) {
+ renameDialog.setDeleteInProgress(mRemovalSidecar.inProgress());
}
mFingerprintsRenaming = new HashMap();
@@ -380,7 +383,8 @@ public class FingerprintSettings extends SubSettings {
final int fingerprintCount = items.size();
for (int i = 0; i < fingerprintCount; i++) {
final Fingerprint item = items.get(i);
- FingerprintPreference pref = new FingerprintPreference(root.getContext());
+ FingerprintPreference pref = new FingerprintPreference(root.getContext(),
+ this /* onDeleteClickListener */);
pref.setKey(genKey(item.getFingerId()));
pref.setTitle(item.getName());
pref.setFingerprint(item);
@@ -471,14 +475,40 @@ public class FingerprintSettings extends SubSettings {
} else if (pref instanceof FingerprintPreference) {
FingerprintPreference fpref = (FingerprintPreference) pref;
final Fingerprint fp = fpref.getFingerprint();
- showRenameDeleteDialog(fp);
- return super.onPreferenceTreeClick(pref);
+ showRenameDialog(fp);
}
- return true;
+ return super.onPreferenceTreeClick(pref);
}
- private void showRenameDeleteDialog(final Fingerprint fp) {
- RenameDeleteDialog renameDeleteDialog = new RenameDeleteDialog();
+ @Override
+ public void onDeleteClick(FingerprintPreference p) {
+ final boolean hasMultipleFingerprint =
+ mFingerprintManager.getEnrolledFingerprints(mUserId).size() > 1;
+ final Fingerprint fp = p.getFingerprint();
+
+ if (hasMultipleFingerprint) {
+ if (mRemovalSidecar.inProgress()) {
+ Log.d(TAG, "Fingerprint delete in progress, skipping");
+ return;
+ }
+ DeleteFingerprintDialog.newInstance(fp, this /* target */)
+ .show(getFragmentManager(), DeleteFingerprintDialog.class.getName());
+ } else {
+ ConfirmLastDeleteDialog lastDeleteDialog = new ConfirmLastDeleteDialog();
+ final boolean isProfileChallengeUser =
+ UserManager.get(getContext()).isManagedProfile(mUserId);
+ final Bundle args = new Bundle();
+ args.putParcelable("fingerprint", fp);
+ args.putBoolean("isProfileChallengeUser", isProfileChallengeUser);
+ lastDeleteDialog.setArguments(args);
+ lastDeleteDialog.setTargetFragment(this, 0);
+ lastDeleteDialog.show(getFragmentManager(),
+ ConfirmLastDeleteDialog.class.getName());
+ }
+ }
+
+ private void showRenameDialog(final Fingerprint fp) {
+ RenameDialog renameDialog = new RenameDialog();
Bundle args = new Bundle();
if (mFingerprintsRenaming.containsKey(fp.getFingerId())) {
final Fingerprint f = new Fingerprint(mFingerprintsRenaming.get(fp.getFingerId()),
@@ -487,10 +517,10 @@ public class FingerprintSettings extends SubSettings {
} else {
args.putParcelable("fingerprint", fp);
}
- renameDeleteDialog.setDeleteInProgress(mRemovalSidecar.inProgress());
- renameDeleteDialog.setArguments(args);
- renameDeleteDialog.setTargetFragment(this, 0);
- renameDeleteDialog.show(getFragmentManager(), RenameDeleteDialog.class.getName());
+ renameDialog.setDeleteInProgress(mRemovalSidecar.inProgress());
+ renameDialog.setArguments(args);
+ renameDialog.setTargetFragment(this, 0);
+ renameDialog.show(getFragmentManager(), RenameDialog.class.getName());
}
@Override
@@ -598,7 +628,8 @@ public class FingerprintSettings extends SubSettings {
}
}
- private void deleteFingerPrint(Fingerprint fingerPrint) {
+ @VisibleForTesting
+ void deleteFingerPrint(Fingerprint fingerPrint) {
mRemovalSidecar.startRemove(fingerPrint, mUserId);
String name = genKey(fingerPrint.getFingerId());
Preference prefToRemove = findPreference(name);
@@ -622,7 +653,60 @@ public class FingerprintSettings extends SubSettings {
}
};
- public static class RenameDeleteDialog extends InstrumentedDialogFragment {
+ public static class DeleteFingerprintDialog extends InstrumentedDialogFragment
+ implements DialogInterface.OnClickListener {
+
+ private static final String KEY_FINGERPRINT = "fingerprint";
+ private Fingerprint mFp;
+ private AlertDialog mAlertDialog;
+
+ public static DeleteFingerprintDialog newInstance(Fingerprint fp,
+ FingerprintSettingsFragment target) {
+ final DeleteFingerprintDialog dialog = new DeleteFingerprintDialog();
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(KEY_FINGERPRINT, fp);
+ dialog.setArguments(bundle);
+ dialog.setTargetFragment(target, 0 /* requestCode */);
+ return dialog;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DIALOG_FINGERPINT_EDIT;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mFp = getArguments().getParcelable(KEY_FINGERPRINT);
+ final String title = getString(R.string.fingerprint_delete_title, mFp.getName());
+
+ mAlertDialog = new AlertDialog.Builder(getActivity())
+ .setTitle(title)
+ .setMessage(R.string.fingerprint_delete_message)
+ .setPositiveButton(
+ R.string.security_settings_fingerprint_enroll_dialog_delete,
+ this /* onClickListener */)
+ .setNegativeButton(R.string.cancel, null /* onClickListener */)
+ .create();
+ return mAlertDialog;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ final int fingerprintId = mFp.getFingerId();
+ Log.v(TAG, "Removing fpId=" + fingerprintId);
+ mMetricsFeatureProvider.action(getContext(),
+ MetricsEvent.ACTION_FINGERPRINT_DELETE,
+ fingerprintId);
+ FingerprintSettingsFragment parent
+ = (FingerprintSettingsFragment) getTargetFragment();
+ parent.deleteFingerPrint(mFp);
+ }
+ }
+ }
+
+ public static class RenameDialog extends InstrumentedDialogFragment {
private Fingerprint mFp;
private EditText mDialogTextField;
@@ -636,6 +720,7 @@ public class FingerprintSettings extends SubSettings {
public void setDeleteInProgress(boolean deleteInProgress) {
mDeleteInProgress = deleteInProgress;
}
+
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mFp = getArguments().getParcelable("fingerprint");
@@ -654,10 +739,8 @@ public class FingerprintSettings extends SubSettings {
final String newName =
mDialogTextField.getText().toString();
final CharSequence name = mFp.getName();
- if (!newName.equals(name)) {
- if (DEBUG) {
- Log.v(TAG, "rename " + name + " to " + newName);
- }
+ if (!TextUtils.equals(newName, name)) {
+ Log.d(TAG, "rename " + name + " to " + newName);
mMetricsFeatureProvider.action(getContext(),
MetricsEvent.ACTION_FINGERPRINT_RENAME,
mFp.getFingerId());
@@ -670,14 +753,6 @@ public class FingerprintSettings extends SubSettings {
dialog.dismiss();
}
})
- .setNegativeButton(
- R.string.security_settings_fingerprint_enroll_dialog_delete,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- onDeleteClick(dialog);
- }
- })
.create();
mAlertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
@@ -711,29 +786,6 @@ public class FingerprintSettings extends SubSettings {
}
}
- private void onDeleteClick(DialogInterface dialog) {
- if (DEBUG) Log.v(TAG, "Removing fpId=" + mFp.getFingerId());
- mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_DELETE,
- mFp.getFingerId());
- FingerprintSettingsFragment parent
- = (FingerprintSettingsFragment) getTargetFragment();
- final boolean isProfileChallengeUser =
- UserManager.get(getContext()).isManagedProfile(parent.mUserId);
- if (parent.mFingerprintManager.getEnrolledFingerprints(parent.mUserId).size() > 1) {
- parent.deleteFingerPrint(mFp);
- } else {
- ConfirmLastDeleteDialog lastDeleteDialog = new ConfirmLastDeleteDialog();
- Bundle args = new Bundle();
- args.putParcelable("fingerprint", mFp);
- args.putBoolean("isProfileChallengeUser", isProfileChallengeUser);
- lastDeleteDialog.setArguments(args);
- lastDeleteDialog.setTargetFragment(getTargetFragment(), 0);
- lastDeleteDialog.show(getFragmentManager(),
- ConfirmLastDeleteDialog.class.getName());
- }
- dialog.dismiss();
- }
-
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -794,27 +846,26 @@ public class FingerprintSettings extends SubSettings {
}
}
- public static class FingerprintPreference extends Preference {
+ public static class FingerprintPreference extends TwoTargetPreference {
+
+ private final OnDeleteClickListener mOnDeleteClickListener;
+
private Fingerprint mFingerprint;
private View mView;
+ private View mDeleteView;
- public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
- public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ public interface OnDeleteClickListener {
+ void onDeleteClick(FingerprintPreference p);
}
- public FingerprintPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public FingerprintPreference(Context context) {
+ public FingerprintPreference(Context context, OnDeleteClickListener onDeleteClickListener) {
super(context);
+ mOnDeleteClickListener = onDeleteClickListener;
}
- public View getView() { return mView; }
+ public View getView() {
+ return mView;
+ }
public void setFingerprint(Fingerprint item) {
mFingerprint = item;
@@ -824,12 +875,26 @@ public class FingerprintSettings extends SubSettings {
return mFingerprint;
}
+ @Override
+ protected int getSecondTargetResId() {
+ return R.layout.preference_widget_delete;
+ }
+
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
mView = view.itemView;
+ mDeleteView = view.itemView.findViewById(R.id.delete_button);
+ mDeleteView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mOnDeleteClickListener != null) {
+ mOnDeleteClickListener.onDeleteClick(FingerprintPreference.this);
+ }
+ }
+ });
}
- };
+ }
private static class LearnMoreSpan extends URLSpan {
diff --git a/tests/robotests/src/com/android/settings/fingerprint/DeleteFingerprintDialogTest.java b/tests/robotests/src/com/android/settings/fingerprint/DeleteFingerprintDialogTest.java
new file mode 100644
index 00000000000..c3c1b42fefb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fingerprint/DeleteFingerprintDialogTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.app.Dialog;
+import android.hardware.fingerprint.Fingerprint;
+
+import com.android.settings.TestConfig;
+import com.android.settings.fingerprint.FingerprintSettings.FingerprintSettingsFragment;
+import com.android.settings.fingerprint.FingerprintSettings.FingerprintSettingsFragment
+ .DeleteFingerprintDialog;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.ShadowEventLogWriter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.FragmentTestUtil;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = {
+ ShadowEventLogWriter.class
+ })
+public class DeleteFingerprintDialogTest {
+
+ @Mock
+ private FingerprintSettingsFragment mTarget;
+ @Mock
+ private Fingerprint mFingerprint;
+ private DeleteFingerprintDialog mFragment;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFragment = DeleteFingerprintDialog.newInstance(mFingerprint, mTarget);
+ }
+
+ @Test
+ public void launchDialog_clickPositive_shouldDeleteFingerprint() {
+ FragmentTestUtil.startFragment(mFragment);
+
+ mFragment.onClick(mFragment.getDialog(), Dialog.BUTTON_POSITIVE);
+
+ verify(mTarget).deleteFingerPrint(mFingerprint);
+ }
+
+ @Test
+ public void launchDialog_clickNegative_shouldDoNothing() {
+ FragmentTestUtil.startFragment(mFragment);
+
+ mFragment.onClick(mFragment.getDialog(), Dialog.BUTTON_NEGATIVE);
+
+ verify(mTarget, never()).deleteFingerPrint(mFingerprint);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fingerprint/FingerprintPreferenceTest.java b/tests/robotests/src/com/android/settings/fingerprint/FingerprintPreferenceTest.java
new file mode 100644
index 00000000000..29c6f10d666
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fingerprint/FingerprintPreferenceTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.content.Context;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.fingerprint.FingerprintSettings.FingerprintPreference;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class FingerprintPreferenceTest {
+
+ @Mock
+ private FingerprintPreference.OnDeleteClickListener mOnDeleteClickListener;
+
+ private Context mContext;
+ private FingerprintPreference mPreference;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mPreference = new FingerprintPreference(mContext, mOnDeleteClickListener);
+ }
+
+ @Test
+ public void shouldShowDeleteButton() {
+ assertThat(mPreference.getSecondTargetResId()).isEqualTo(R.layout.preference_widget_delete);
+ }
+
+ @Test
+ public void bindAndClickDeleteButton_shouldInvokeOnDeleteListener() {
+ final FrameLayout layout = new FrameLayout(mContext);
+ final View deleteButton = LayoutInflater.from(mContext)
+ .inflate(mPreference.getSecondTargetResId(), layout, true);
+ final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(layout);
+ mPreference.onBindViewHolder(holder);
+
+ layout.findViewById(R.id.delete_button).performClick();
+
+ verify(mOnDeleteClickListener).onDeleteClick(mPreference);
+ }
+}