Stay in face settings unless the user explicitly backs out

Bug: 130898604

Test: Builds
Change-Id: I7159c3a6259d298f78b8328b2c6974ba46fb56f9
diff --git a/res/layout/face_enroll_button.xml b/res/layout/face_enroll_button.xml
new file mode 100644
index 0000000..2a6d676
--- /dev/null
+++ b/res/layout/face_enroll_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <Button
+        android:id="@+id/security_settings_face_settings_enroll_button"
+        android:layout_marginStart="@dimen/screen_margin_sides"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start"
+        android:text="@string/security_settings_face_settings_enroll"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fae59e6..4b29adb 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -948,6 +948,8 @@
     <string name="security_settings_face_settings_require_confirmation_details">When authenticating in apps, always require confirmation</string>
     <!-- Button text in face settings which removes the user's faces from the device [CHAR LIMIT=20] -->
     <string name="security_settings_face_settings_remove_face_data">Remove face data</string>
+    <!-- Button text in face settings which lets the user enroll their face [CHAR LIMIT=40] -->
+    <string name="security_settings_face_settings_enroll">Set up face authentication</string>
     <!-- Text shown in face settings explaining what your face can be used for. [CHAR LIMIT=NONE] -->
     <string name="security_settings_face_settings_footer">Your face can be used to unlock your device and access apps.
         <annotation id="url">Learn more</annotation></string>
diff --git a/res/xml/security_settings_face.xml b/res/xml/security_settings_face.xml
index ad37a3e..39bf73b 100644
--- a/res/xml/security_settings_face.xml
+++ b/res/xml/security_settings_face.xml
@@ -61,6 +61,11 @@
             android:key="security_settings_face_delete_faces_container"
             android:selectable="false"
             android:layout="@layout/face_remove_button" />
+
+        <com.android.settingslib.widget.LayoutPreference
+            android:key="security_settings_face_enroll_faces_container"
+            android:selectable="false"
+            android:layout="@layout/face_enroll_button " />
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java
index a961921..52eaa17 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollBase.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java
@@ -76,7 +76,7 @@
     public static final int BIOMETRIC_FIND_SENSOR_REQUEST = 2;
     public static final int LEARN_MORE_REQUEST = 3;
     public static final int CONFIRM_REQUEST = 4;
-    public static final int ENROLLING = 5;
+    public static final int ENROLL_REQUEST = 5;
 
     protected boolean mLaunchedConfirmLock;
     protected byte[] mToken;
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index b65a31d..f5216d8 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -31,6 +31,8 @@
 import android.provider.SearchIndexableResource;
 import android.util.Log;
 
+import androidx.preference.Preference;
+
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.Utils;
@@ -60,12 +62,23 @@
     private byte[] mToken;
     private FaceSettingsAttentionPreferenceController mAttentionController;
     private FaceSettingsRemoveButtonPreferenceController mRemoveController;
+    private FaceSettingsEnrollButtonPreferenceController mEnrollController;
     private List<AbstractPreferenceController> mControllers;
 
+    private List<Preference> mTogglePreferences;
+    private Preference mRemoveButton;
+    private Preference mEnrollButton;
+
     private final FaceSettingsRemoveButtonPreferenceController.Listener mRemovalListener = () -> {
-        if (getActivity() != null) {
-            getActivity().finish();
+
+        // Disable the toggles until the user re-enrolls
+        for (Preference preference : mTogglePreferences) {
+            preference.setEnabled(false);
         }
+
+        // Hide the "remove" button and show the "set up face authentication" button.
+        mRemoveButton.setVisible(false);
+        mEnrollButton.setVisible(true);
     };
 
     public static boolean isAvailable(Context context) {
@@ -103,10 +116,22 @@
         mUserId = getActivity().getIntent().getIntExtra(
                 Intent.EXTRA_USER_ID, UserHandle.myUserId());
 
+        Preference keyguardPref = findPreference(FaceSettingsKeyguardPreferenceController.KEY);
+        Preference appPref = findPreference(FaceSettingsAppPreferenceController.KEY);
+        Preference attentionPref = findPreference(FaceSettingsAttentionPreferenceController.KEY);
+        Preference confirmPref = findPreference(FaceSettingsConfirmPreferenceController.KEY);
+        mTogglePreferences = new ArrayList<>(
+                Arrays.asList(keyguardPref, appPref, attentionPref, confirmPref));
+
+        mRemoveButton = findPreference(FaceSettingsRemoveButtonPreferenceController.KEY);
+        mEnrollButton = findPreference(FaceSettingsEnrollButtonPreferenceController.KEY);
+
         // There is no better way to do this :/
         for (AbstractPreferenceController controller : mControllers) {
             if (controller instanceof  FaceSettingsPreferenceController) {
                 ((FaceSettingsPreferenceController) controller).setUserId(mUserId);
+            } else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) {
+                ((FaceSettingsEnrollButtonPreferenceController) controller).setUserId(mUserId);
             }
         }
         mRemoveController.setUserId(mUserId);
@@ -137,7 +162,12 @@
         super.onResume();
         if (mToken != null) {
             mAttentionController.setToken(mToken);
+            mEnrollController.setToken(mToken);
         }
+
+        final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
+        mEnrollButton.setVisible(!hasEnrolled);
+        mRemoveButton.setVisible(hasEnrolled);
     }
 
     @Override
@@ -152,6 +182,7 @@
                             ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
                     if (mToken != null) {
                         mAttentionController.setToken(mToken);
+                        mEnrollController.setToken(mToken);
                     }
                 }
             }
@@ -188,6 +219,9 @@
                 mRemoveController = (FaceSettingsRemoveButtonPreferenceController) controller;
                 mRemoveController.setListener(mRemovalListener);
                 mRemoveController.setActivity((SettingsActivity) getActivity());
+            } else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) {
+                mEnrollController = (FaceSettingsEnrollButtonPreferenceController) controller;
+                mEnrollController.setActivity((SettingsActivity) getActivity());
             }
         }
 
@@ -204,6 +238,7 @@
         controllers.add(new FaceSettingsRemoveButtonPreferenceController(context));
         controllers.add(new FaceSettingsFooterPreferenceController(context));
         controllers.add(new FaceSettingsConfirmPreferenceController(context));
+        controllers.add(new FaceSettingsEnrollButtonPreferenceController(context));
         return controllers;
     }
 
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAppPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAppPreferenceController.java
index 78389b6..1ffcb4c 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsAppPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsAppPreferenceController.java
@@ -19,22 +19,28 @@
 import static android.provider.Settings.Secure.FACE_UNLOCK_APP_ENABLED;
 
 import android.content.Context;
+import android.hardware.face.FaceManager;
 import android.provider.Settings;
 
+import androidx.preference.Preference;
+
 /**
  * Preference controller for Face settings page controlling the ability to use
  * Face authentication in apps (through BiometricPrompt).
  */
 public class FaceSettingsAppPreferenceController extends FaceSettingsPreferenceController {
 
-    private static final String KEY = "security_settings_face_app";
+    static final String KEY = "security_settings_face_app";
 
     private static final int ON = 1;
     private static final int OFF = 0;
     private static final int DEFAULT = ON;  // face unlock is enabled for BiometricPrompt by default
 
+    private FaceManager mFaceManager;
+
     public FaceSettingsAppPreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
+        mFaceManager = context.getSystemService(FaceManager.class);
     }
 
     public FaceSettingsAppPreferenceController(Context context) {
@@ -57,6 +63,18 @@
     }
 
     @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        if (!FaceSettings.isAvailable(mContext)) {
+            preference.setEnabled(false);
+        } else if (!mFaceManager.hasEnrolledTemplates(getUserId())) {
+            preference.setEnabled(false);
+        } else {
+            preference.setEnabled(true);
+        }
+    }
+
+    @Override
     public int getAvailabilityStatus() {
         return AVAILABLE;
     }
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
index 9e4fd38..ef90b1e 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
@@ -60,8 +60,12 @@
         @Override
         public void onCompleted(boolean success, int feature, boolean value) {
             if (feature == FaceManager.FEATURE_REQUIRE_ATTENTION && success) {
-                mPreference.setEnabled(true);
-                mPreference.setChecked(value);
+                if (!mFaceManager.hasEnrolledTemplates(getUserId())) {
+                    mPreference.setEnabled(false);
+                } else {
+                    mPreference.setEnabled(true);
+                    mPreference.setChecked(value);
+                }
             }
         }
     };
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
index b721072..7dbe557 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
@@ -19,8 +19,11 @@
 import static android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION;
 
 import android.content.Context;
+import android.hardware.face.FaceManager;
 import android.provider.Settings;
 
+import androidx.preference.Preference;
+
 import com.android.settings.core.TogglePreferenceController;
 
 /**
@@ -28,12 +31,14 @@
  */
 public class FaceSettingsConfirmPreferenceController extends FaceSettingsPreferenceController {
 
-    private static final String KEY = "security_settings_face_require_confirmation";
+    static final String KEY = "security_settings_face_require_confirmation";
 
     private static final int ON = 1;
     private static final int OFF = 0;
     private static final int DEFAULT = OFF;
 
+    private FaceManager mFaceManager;
+
     public FaceSettingsConfirmPreferenceController(Context context) {
         this(context, KEY);
     }
@@ -41,6 +46,7 @@
     public FaceSettingsConfirmPreferenceController(Context context,
             String preferenceKey) {
         super(context, preferenceKey);
+        mFaceManager = context.getSystemService(FaceManager.class);
     }
 
     @Override
@@ -56,6 +62,18 @@
     }
 
     @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        if (!FaceSettings.isAvailable(mContext)) {
+            preference.setEnabled(false);
+        } else if (!mFaceManager.hasEnrolledTemplates(getUserId())) {
+            preference.setEnabled(false);
+        } else {
+            preference.setEnabled(true);
+        }
+    }
+
+    @Override
     public int getAvailabilityStatus() {
         return AVAILABLE;
     }
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java
new file mode 100644
index 0000000..ec7b194
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 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.biometrics.face;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settingslib.widget.LayoutPreference;
+
+/**
+ * Preference controller that allows a user to enroll their face.
+ */
+public class FaceSettingsEnrollButtonPreferenceController extends BasePreferenceController
+        implements View.OnClickListener {
+
+    private static final String TAG = "FaceSettings/Remove";
+    static final String KEY = "security_settings_face_enroll_faces_container";
+
+    private int mUserId;
+    private byte[] mToken;
+    private SettingsActivity mActivity;
+    private Button mButton;
+
+    public FaceSettingsEnrollButtonPreferenceController(Context context) {
+        this(context, KEY);
+    }
+
+    public FaceSettingsEnrollButtonPreferenceController(Context context,
+            String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+
+        mButton = ((LayoutPreference) preference)
+                .findViewById(R.id.security_settings_face_settings_enroll_button);
+        mButton.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        final Intent intent = new Intent();
+        intent.setClassName("com.android.settings", FaceEnrollIntroduction.class.getName());
+        intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
+        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
+        mContext.startActivity(intent);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    public void setUserId(int userId) {
+        mUserId = userId;
+    }
+
+    public void setToken(byte[] token) {
+        mToken = token;
+    }
+
+    public void setActivity(SettingsActivity activity) {
+        mActivity = activity;
+    }
+}
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardPreferenceController.java
index 3a7865d..92eab85 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardPreferenceController.java
@@ -20,6 +20,7 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.hardware.face.FaceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 
@@ -39,8 +40,11 @@
     private static final int OFF = 0;
     private static final int DEFAULT = ON;  // face unlock is enabled on keyguard by default
 
+    private FaceManager mFaceManager;
+
     public FaceSettingsKeyguardPreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
+        mFaceManager = context.getSystemService(FaceManager.class);
     }
 
     public FaceSettingsKeyguardPreferenceController(Context context) {
@@ -76,6 +80,8 @@
             preference.setEnabled(false);
         } else if (adminDisabled()) {
             preference.setEnabled(false);
+        } else if (!mFaceManager.hasEnrolledTemplates(getUserId())) {
+            preference.setEnabled(false);
         } else {
             preference.setEnabled(true);
         }
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
index 600ea37..68ca259 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
@@ -24,7 +24,6 @@
 import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.os.Bundle;
-import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
@@ -48,7 +47,7 @@
         implements View.OnClickListener {
 
     private static final String TAG = "FaceSettings/Remove";
-    private static final String KEY = "security_settings_face_delete_faces_container";
+    static final String KEY = "security_settings_face_delete_faces_container";
 
     public static class ConfirmRemoveDialog extends InstrumentedDialogFragment {
 
@@ -85,6 +84,7 @@
     private Listener mListener;
     private SettingsActivity mActivity;
     private int mUserId;
+    private boolean mRemoving;
 
     private final Context mContext;
     private final FaceManager mFaceManager;
@@ -103,6 +103,7 @@
                 if (!faces.isEmpty()) {
                     mButton.setEnabled(true);
                 } else {
+                    mRemoving = false;
                     mListener.onRemoved();
                 }
             } else {
@@ -154,6 +155,12 @@
         mButton = ((LayoutPreference) preference)
                 .findViewById(R.id.security_settings_face_settings_remove_button);
         mButton.setOnClickListener(this);
+
+        if (!FaceSettings.isAvailable(mContext)) {
+            mButton.setEnabled(false);
+        } else {
+            mButton.setEnabled(!mRemoving);
+        }
     }
 
     @Override
@@ -169,6 +176,7 @@
     @Override
     public void onClick(View v) {
         if (v == mButton) {
+            mRemoving = true;
             mButton.setEnabled(false);
             ConfirmRemoveDialog dialog = new ConfirmRemoveDialog();
             dialog.setOnClickListener(mOnClickListener);
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index 5589592..733fb3f 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -147,7 +147,7 @@
             getSupportFragmentManager().beginTransaction().remove(mSidecar).
                     commitAllowingStateLoss();
             mSidecar = null;
-            startActivityForResult(getFingerprintEnrollingIntent(), ENROLLING);
+            startActivityForResult(getFingerprintEnrollingIntent(), ENROLL_REQUEST);
         }
     }
 
@@ -162,7 +162,7 @@
             } else {
                 finish();
             }
-        } else if (requestCode == ENROLLING) {
+        } else if (requestCode == ENROLL_REQUEST) {
             if (resultCode == RESULT_FINISHED) {
                 setResult(RESULT_FINISHED);
                 finish();