Merge "Update enabled state of ZenRulePreference"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 84b14d0..f9dc83b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3249,6 +3249,9 @@
         <activity android:name=".homepage.contextualcards.ContextualCardFeedbackDialog"
                   android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
 
+        <activity android:name=".homepage.contextualcards.FaceReEnrollDialog"
+                  android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+
         <activity
             android:name="Settings$WifiCallingDisclaimerActivity"
             android:label="@string/wifi_calling_settings_title"
diff --git a/res/layout/locale_drag_cell.xml b/res/layout/locale_drag_cell.xml
index db9454a..7b932f3 100644
--- a/res/layout/locale_drag_cell.xml
+++ b/res/layout/locale_drag_cell.xml
@@ -16,7 +16,6 @@
 
 <com.android.settings.localepicker.LocaleDragCell
               xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:tools="http://schemas.android.com/tools"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:minHeight="?android:listPreferredItemHeight"
@@ -27,22 +26,19 @@
     <CheckBox
         android:id="@+id/checkbox"
         style="@style/LanguageCheckboxAndLabel"
-        tools:text="French"
-        android:paddingStart="16dp"
+        android:paddingStart="24dp"
         android:layout_toStartOf="@+id/dragHandle"/>
 
     <TextView
         android:id="@+id/label"
         style="@style/LanguageCheckboxAndLabel"
-        tools:text="French"
-        android:paddingStart="48dp"
+        android:paddingStart="56dp"
         android:layout_toStartOf="@+id/dragHandle"/>
 
     <TextView
         android:id="@+id/miniLabel"
         style="@style/LanguageCheckboxAndLabel"
         android:layout_width="wrap_content"
-        tools:text="22"
         android:textColor="?android:attr/colorAccent"
         android:minWidth="24sp"
         android:gravity="center_vertical|center_horizontal"
@@ -53,7 +49,7 @@
         android:id="@+id/l10nWarn"
         style="@style/LanguageCheckboxAndLabel"
         android:layout_marginTop="-28dp"
-        android:paddingStart="48dp"
+        android:paddingStart="56dp"
         android:textAppearance="?android:attr/textAppearanceListItemSecondary"
         android:textColor="?android:textColorSecondary"
         android:text="@string/locale_not_translated"
diff --git a/res/layout/locale_order_list.xml b/res/layout/locale_order_list.xml
index d2ad072..d096368 100644
--- a/res/layout/locale_order_list.xml
+++ b/res/layout/locale_order_list.xml
@@ -23,9 +23,7 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp">
+        android:orientation="vertical">
 
         <com.android.settings.localepicker.LocaleRecyclerView
             android:id="@+id/dragList"
@@ -40,7 +38,7 @@
             android:paddingStart="?android:attr/listPreferredItemPaddingStart"
             android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
             android:drawableStart="@drawable/ic_add_24dp"
-            android:drawablePadding="24dp"
+            android:drawablePadding="32dp"
             android:textAlignment="textStart"
             android:text="@string/add_a_language"
             style="@style/Base.Widget.AppCompat.Button.Borderless"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b545045..7479318 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -949,6 +949,18 @@
     <string name="security_settings_face_enroll_finish_title">All set. Looking good.</string>
     <!-- Button text to exit face wizard after everything is done [CHAR LIMIT=15] -->
     <string name="security_settings_face_enroll_done">Done</string>
+    <!-- Settings suggestion title text for re-enrolling a face. [CHAR LIMIT=50] -->
+    <string name="security_settings_face_enroll_should_re_enroll_title">Improve face unlock performance</string>
+    <!-- Settings suggestion subtitle text for re-enrolling a face. [CHAR LIMIT=40] -->
+    <string name="security_settings_face_enroll_should_re_enroll_subtitle">Set up face unlock again</string>
+    <!-- Settings suggestion title text for mandatory re-enrolling of a face. [CHAR LIMIT=50] -->
+    <string name="security_settings_face_enroll_must_re_enroll_title">Set up face unlock again</string>
+    <!-- Settings suggestion subtitle text for mandatory re-enrolling of a face. [CHAR LIMIT=40] -->
+    <string name="security_settings_face_enroll_must_re_enroll_subtitle">Improve security and performance</string>
+    <!-- Settings suggestion alert body title for re-enrolling a face. [CHAR LIMIT=60] -->
+    <string name="security_settings_face_enroll_improve_face_alert_title">Set up face unlock</string>
+    <!-- Settings suggestion alert body text for re-enrolling a face. [CHAR LIMIT=300] -->
+    <string name="security_settings_face_enroll_improve_face_alert_body">Delete your current face data to set up face unlock again.\n\nThe face data used by face unlock will be permanently and securely deleted. After removal, you will need your PIN, pattern, or password to unlock your phone, sign in to apps, and confirm payments.</string>
     <!-- Title for a category shown for the face settings page. [CHAR LIMIT=20] -->
     <string name="security_settings_face_settings_use_face_category">Use face unlock for</string>
     <!-- Text shown on a toggle which allows or disallows the device to use face for unlocking the device. [CHAR LIMIT=20] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a72346d..68fa764 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -655,4 +655,9 @@
         <item name="android:clipChildren">true</item>
     </style>
 
+    <style name="PickerDialogTheme.Settings" parent="@*android:style/ThemeOverlay.Material.Dialog.DatePicker">
+        <item name="android:clipToPadding">true</item>
+        <item name="android:clipChildren">true</item>
+    </style>
+
 </resources>
diff --git a/res/values/styles_preference.xml b/res/values/styles_preference.xml
index 1f4421f..3e6f772 100644
--- a/res/values/styles_preference.xml
+++ b/res/values/styles_preference.xml
@@ -27,6 +27,8 @@
         <item name="twoStateButtonPreferenceStyle">@style/TwoStateButtonPreference</item>
         <item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle
         </item>
+        <!-- For preference category color -->
+        <item name="preferenceCategoryTitleTextColor">?android:attr/textColorSecondary</item>
         <item name="preferenceFragmentCompatStyle">@style/SettingsPreferenceFragmentStyle</item>
     </style>
 
diff --git a/res/values/themes.xml b/res/values/themes.xml
index b96fd86..9c50ceb 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -32,6 +32,7 @@
         <item name="android:listPreferredItemHeight">72dip</item>
         <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
+        <item name="android:datePickerDialogTheme">@style/PickerDialogTheme.Settings</item>
 
         <item name="fingerprint_layout_theme">@style/FingerprintLayoutTheme</item>
         <item name="face_layout_theme">@style/FaceLayoutTheme</item>
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
index e3b5c05..f4433f0 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
@@ -81,7 +81,7 @@
             final RequireScrollMixin requireScrollMixin = getLayout().getMixin(
                     RequireScrollMixin.class);
             requireScrollMixin.requireScrollWithButton(this, agreeButton,
-                    R.string.sud_more_button_label,
+                    R.string.wifi_more,
                     button -> {
                         onNextButtonClick(button);
                     });
diff --git a/src/com/android/settings/homepage/contextualcards/FaceReEnrollDialog.java b/src/com/android/settings/homepage/contextualcards/FaceReEnrollDialog.java
new file mode 100644
index 0000000..46ba26d
--- /dev/null
+++ b/src/com/android/settings/homepage/contextualcards/FaceReEnrollDialog.java
@@ -0,0 +1,122 @@
+/*
+ * 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.homepage.contextualcards;
+
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice;
+
+/**
+ * This class is used to show a popup dialog for {@link FaceSetupSlice}.
+ */
+public class FaceReEnrollDialog extends AlertActivity implements
+        DialogInterface.OnClickListener {
+
+    private static final String TAG = "FaceReEnrollDialog";
+
+    private static final String BIOMETRIC_ENROLL_ACTION = "android.settings.BIOMETRIC_ENROLL";
+
+    private FaceManager mFaceManager;
+    /**
+     * The type of re-enrollment that has been requested,
+     * see {@link Settings.Secure#FACE_UNLOCK_RE_ENROLL} for more details.
+     */
+    private int mReEnrollType;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final AlertController.AlertParams alertParams = mAlertParams;
+        alertParams.mTitle = getText(
+                R.string.security_settings_face_enroll_improve_face_alert_title);
+        alertParams.mMessage = getText(
+                R.string.security_settings_face_enroll_improve_face_alert_body);
+        alertParams.mPositiveButtonText = getText(R.string.storage_menu_set_up);
+        alertParams.mNegativeButtonText = getText(R.string.cancel);
+        alertParams.mPositiveButtonListener = this;
+
+        mFaceManager = Utils.getFaceManagerOrNull(getApplicationContext());
+
+        final Context context = getApplicationContext();
+        mReEnrollType = FaceSetupSlice.getReEnrollSetting(context, getUserId());
+
+        Log.d(TAG, "ReEnroll Type : " + mReEnrollType);
+        if (mReEnrollType == FaceSetupSlice.FACE_RE_ENROLL_SUGGESTED) {
+            // setupAlert will actually display the popup dialog.
+            setupAlert();
+        } else if (mReEnrollType == FaceSetupSlice.FACE_RE_ENROLL_REQUIRED) {
+            // in this case we are skipping the popup dialog and directly going to the
+            // re enrollment flow. A grey overlay will appear to indicate that we are
+            // transitioning.
+            removeFaceAndReEnroll();
+        } else {
+            Log.d(TAG, "Error unsupported flow for : " + mReEnrollType);
+            dismiss();
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        removeFaceAndReEnroll();
+    }
+
+    public void removeFaceAndReEnroll() {
+        final int userId = getUserId();
+        if (mFaceManager == null || !mFaceManager.hasEnrolledTemplates(userId)) {
+            finish();
+        }
+        mFaceManager.remove(new Face("", 0, 0), userId, new FaceManager.RemovalCallback() {
+            @Override
+            public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
+                super.onRemovalError(face, errMsgId, errString);
+                finish();
+            }
+
+            @Override
+            public void onRemovalSucceeded(Face face, int remaining) {
+                super.onRemovalSucceeded(face, remaining);
+                if (remaining != 0) {
+                    return;
+                }
+                // Send user to the enroll flow.
+                final Intent reEnroll = new Intent(BIOMETRIC_ENROLL_ACTION);
+                final Context context = getApplicationContext();
+
+                try {
+                    startActivity(reEnroll);
+                } catch (Exception e) {
+                    Log.e(TAG, "Failed to startActivity");
+                }
+
+                finish();
+            }
+        });
+    }
+}
diff --git a/src/com/android/settings/homepage/contextualcards/slices/FaceSetupSlice.java b/src/com/android/settings/homepage/contextualcards/slices/FaceSetupSlice.java
index 112f655..2e34824 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/FaceSetupSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/FaceSetupSlice.java
@@ -17,17 +17,14 @@
 package com.android.settings.homepage.contextualcards.slices;
 
 
-import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
-
 import android.app.PendingIntent;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.face.FaceManager;
 import android.net.Uri;
 import android.os.UserHandle;
+import android.provider.Settings;
 
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.slice.Slice;
@@ -39,14 +36,40 @@
 import com.android.settings.SubSettings;
 import com.android.settings.Utils;
 import com.android.settings.biometrics.face.FaceStatusPreferenceController;
+import com.android.settings.homepage.contextualcards.FaceReEnrollDialog;
 import com.android.settings.security.SecuritySettings;
 import com.android.settings.slices.CustomSliceRegistry;
 import com.android.settings.slices.CustomSliceable;
 import com.android.settings.slices.SliceBuilderUtils;
 
+/**
+ * This class is used for showing re-enroll suggestions in the Settings page. By either having an
+ * un-enrolled user or setting {@link Settings.Secure#FACE_UNLOCK_RE_ENROLL} to one of the
+ * states listed in {@link Settings.Secure} the slice will change its text and potentially show
+ * a {@link FaceReEnrollDialog}.
+ */
 public class FaceSetupSlice implements CustomSliceable {
 
     private final Context mContext;
+    /**
+     * If a user currently is not enrolled then this class will show a recommendation to
+     * enroll their face.
+     */
+    private FaceManager mFaceManager;
+
+    /**
+     * Various states the {@link FaceSetupSlice} can be in,
+     * See {@link Settings.Secure#FACE_UNLOCK_RE_ENROLL} for more details.
+     */
+
+    // No re-enrollment.
+    public static final int FACE_NO_RE_ENROLL_REQUIRED = 0;
+    // Re enrollment is suggested.
+    public static final int FACE_RE_ENROLL_SUGGESTED = 1;
+    // Re enrollment is required after a set time period.
+    public static final int FACE_RE_ENROLL_AFTER_TIMEOUT = 2;
+    // Re enrollment is required immediately.
+    public static final int FACE_RE_ENROLL_REQUIRED = 3;
 
     public FaceSetupSlice(Context context) {
         mContext = context;
@@ -54,21 +77,45 @@
 
     @Override
     public Slice getSlice() {
-        final FaceManager faceManager = Utils.getFaceManagerOrNull(mContext);
-        if (faceManager == null || faceManager.hasEnrolledTemplates(UserHandle.myUserId())) {
-            return null;
+        mFaceManager = Utils.getFaceManagerOrNull(mContext);
+        if (mFaceManager == null) {
+            return new ListBuilder(mContext, CustomSliceRegistry.FACE_ENROLL_SLICE_URI,
+                    ListBuilder.INFINITY).setIsError(true).build();
         }
 
-        final CharSequence title = mContext.getText(
-                R.string.security_settings_face_settings_enroll);
+        final int userId = UserHandle.myUserId();
+        final boolean hasEnrolledTemplates = mFaceManager.hasEnrolledTemplates(userId);
+        final int shouldReEnroll = FaceSetupSlice.getReEnrollSetting(mContext, userId);
+
+        CharSequence title = "";
+        CharSequence subtitle = "";
+
+        // Set the title and subtitle according to the different states, the icon and layout will
+        // stay the same.
+        if (!hasEnrolledTemplates) {
+            title = mContext.getText(R.string.security_settings_face_settings_enroll);
+            subtitle = mContext.getText(
+                    R.string.security_settings_face_settings_context_subtitle);
+        } else if (shouldReEnroll == FACE_RE_ENROLL_SUGGESTED) {
+            title = mContext.getText(
+                    R.string.security_settings_face_enroll_should_re_enroll_title);
+            subtitle = mContext.getText(
+                    R.string.security_settings_face_enroll_should_re_enroll_subtitle);
+        } else if (shouldReEnroll == FACE_RE_ENROLL_REQUIRED) {
+            title = mContext.getText(
+                    R.string.security_settings_face_enroll_must_re_enroll_title);
+            subtitle = mContext.getText(
+                    R.string.security_settings_face_enroll_must_re_enroll_subtitle);
+        } else {
+            return new ListBuilder(mContext, CustomSliceRegistry.FACE_ENROLL_SLICE_URI,
+                    ListBuilder.INFINITY).setIsError(true).build();
+        }
+
         final ListBuilder listBuilder = new ListBuilder(mContext,
                 CustomSliceRegistry.FACE_ENROLL_SLICE_URI, ListBuilder.INFINITY)
                 .setAccentColor(Utils.getColorAccentDefaultColor(mContext));
         final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_face_24dp);
-        return listBuilder
-                .addRow(buildRowBuilder(title,
-                        mContext.getText(R.string.security_settings_face_settings_context_subtitle),
-                        icon, mContext, getIntent()))
+        return listBuilder.addRow(buildRowBuilder(title, subtitle, icon, mContext, getIntent()))
                 .build();
     }
 
@@ -79,12 +126,18 @@
 
     @Override
     public Intent getIntent() {
-        return SliceBuilderUtils.buildSearchResultPageIntent(mContext,
-                SecuritySettings.class.getName(),
-                FaceStatusPreferenceController.KEY_FACE_SETTINGS,
-                mContext.getText(R.string.security_settings_face_settings_enroll).toString(),
-                SettingsEnums.SLICE)
-                .setClassName(mContext.getPackageName(), SubSettings.class.getName());
+        final boolean hasEnrolledTemplates = mFaceManager.hasEnrolledTemplates(
+                UserHandle.myUserId());
+        if (!hasEnrolledTemplates) {
+            return SliceBuilderUtils.buildSearchResultPageIntent(mContext,
+                    SecuritySettings.class.getName(),
+                    FaceStatusPreferenceController.KEY_FACE_SETTINGS,
+                    mContext.getText(R.string.security_settings_face_settings_enroll).toString(),
+                    SettingsEnums.SLICE)
+                    .setClassName(mContext.getPackageName(), SubSettings.class.getName());
+        } else {
+            return new Intent(mContext, FaceReEnrollDialog.class);
+        }
     }
 
     private static RowBuilder buildRowBuilder(CharSequence title, CharSequence subTitle,
@@ -98,4 +151,10 @@
                 .setSubtitle(subTitle)
                 .setPrimaryAction(primarySliceAction);
     }
+
+    public static int getReEnrollSetting(Context context, int userId) {
+        return Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.FACE_UNLOCK_RE_ENROLL, FACE_NO_RE_ENROLL_REQUIRED, userId);
+    }
+
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/FaceSetupSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/FaceSetupSliceTest.java
index 71b5c7a..eeb465b 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/FaceSetupSliceTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/FaceSetupSliceTest.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager;
 import android.hardware.face.FaceManager;
 import android.os.UserHandle;
+import android.provider.Settings;
 
 import androidx.slice.Slice;
 import androidx.slice.SliceProvider;
@@ -59,26 +60,58 @@
     public void getSlice_noFaceManager_shouldReturnNull() {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
         final FaceSetupSlice setupSlice = new FaceSetupSlice(mContext);
+
         assertThat(setupSlice.getSlice()).isNull();
     }
 
     @Test
-    public void getSlice_faceEnrolled_shouldReturnNull() {
+    public void getSlice_faceEnrolled_noReEnroll_shouldReturnNull() {
         final FaceManager faceManager = mock(FaceManager.class);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
         when(faceManager.hasEnrolledTemplates(UserHandle.myUserId())).thenReturn(true);
         when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(faceManager);
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.FACE_UNLOCK_RE_ENROLL,
+                0);
         final FaceSetupSlice setupSlice = new FaceSetupSlice(mContext);
+
         assertThat(setupSlice.getSlice()).isNull();
     }
 
     @Test
-    public void getSlice_faceNotEnrolled_shouldReturnNonNull() {
+    public void getSlice_faceNotEnrolled_shouldReturnSlice() {
         final FaceManager faceManager = mock(FaceManager.class);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
         when(faceManager.hasEnrolledTemplates(UserHandle.myUserId())).thenReturn(false);
         when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(faceManager);
         final FaceSetupSlice setupSlice = new FaceSetupSlice(mContext);
+
         assertThat(setupSlice.getSlice()).isNotNull();
     }
-}
\ No newline at end of file
+
+    @Test
+    public void getSlice_faceEnrolled_shouldReEnroll_shouldReturnSlice() {
+        final FaceManager faceManager = mock(FaceManager.class);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(faceManager.hasEnrolledTemplates(UserHandle.myUserId())).thenReturn(true);
+        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(faceManager);
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.FACE_UNLOCK_RE_ENROLL,
+                1);
+        final FaceSetupSlice setupSlice = new FaceSetupSlice(mContext);
+
+        assertThat(setupSlice.getSlice()).isNotNull();
+    }
+
+    @Test
+    public void getSlice_faceEnrolled_musteEnroll_shouldReturnSlice() {
+        final FaceManager faceManager = mock(FaceManager.class);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(faceManager.hasEnrolledTemplates(UserHandle.myUserId())).thenReturn(true);
+        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(faceManager);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.FACE_UNLOCK_RE_ENROLL,
+                3);
+        final FaceSetupSlice setupSlice = new FaceSetupSlice(mContext);
+
+        assertThat(setupSlice.getSlice()).isNotNull();
+    }
+}