Merge "Polish humanize strings for Accessibility button & gesture page"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6d35545..fcd6cc0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4341,6 +4341,13 @@
                        android:value="true" />
         </activity>
 
+        <receiver android:name=".safetycenter.SafetySourceBroadcastReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES"/>
+            </intent-filter>
+        </receiver>
+
         <!-- This is the longest AndroidManifest.xml ever. -->
     </application>
 </manifest>
diff --git a/res/drawable/accessibility_text_reading_reset_button_background.xml b/res/drawable/accessibility_text_reading_reset_button_background.xml
new file mode 100644
index 0000000..b86facf
--- /dev/null
+++ b/res/drawable/accessibility_text_reading_reset_button_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 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.
+-->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+
+    <corners android:radius="100dp" />
+    <solid android:color="?androidprv:attr/colorAccentPrimary" />
+</shape>
diff --git a/res/drawable/dream_preview_icon.xml b/res/drawable/dream_preview_icon.xml
new file mode 100644
index 0000000..c4bd739
--- /dev/null
+++ b/res/drawable/dream_preview_icon.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M12,16Q13.875,16 15.188,14.688Q16.5,13.375 16.5,11.5Q16.5,9.625 15.188,8.312Q13.875,7 12,7Q10.125,7 8.812,8.312Q7.5,9.625 7.5,11.5Q7.5,13.375 8.812,14.688Q10.125,16 12,16ZM12,14.2Q10.875,14.2 10.088,13.412Q9.3,12.625 9.3,11.5Q9.3,10.375 10.088,9.587Q10.875,8.8 12,8.8Q13.125,8.8 13.913,9.587Q14.7,10.375 14.7,11.5Q14.7,12.625 13.913,13.412Q13.125,14.2 12,14.2ZM12,19Q8.35,19 5.35,16.962Q2.35,14.925 1,11.5Q2.35,8.075 5.35,6.037Q8.35,4 12,4Q15.65,4 18.65,6.037Q21.65,8.075 23,11.5Q21.65,14.925 18.65,16.962Q15.65,19 12,19ZM12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5ZM12,17Q14.825,17 17.188,15.512Q19.55,14.025 20.8,11.5Q19.55,8.975 17.188,7.487Q14.825,6 12,6Q9.175,6 6.812,7.487Q4.45,8.975 3.2,11.5Q4.45,14.025 6.812,15.512Q9.175,17 12,17Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/accessibility_text_reading_reset_button.xml b/res/layout/accessibility_text_reading_reset_button.xml
new file mode 100644
index 0000000..43800df
--- /dev/null
+++ b/res/layout/accessibility_text_reading_reset_button.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+    <Button
+        android:id="@+id/reset_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:background="@drawable/accessibility_text_reading_reset_button_background"
+        android:paddingHorizontal="24dp"
+        android:paddingVertical="14dp"
+        android:text="@string/accessibility_text_reading_reset_button_title"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+</FrameLayout>
diff --git a/res/layout/dream_picker_layout.xml b/res/layout/dream_picker_layout.xml
index 6530ea2..6de7ff6 100644
--- a/res/layout/dream_picker_layout.xml
+++ b/res/layout/dream_picker_layout.xml
@@ -40,17 +40,6 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"/>
 
-            <Button
-                android:id="@+id/preview_button"
-                style="@style/ActionPrimaryButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                android:text="@string/dream_preview_button_title"
-                app:layout_constraintTop_toBottomOf="@+id/dream_list"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintEnd_toEndOf="parent"/>
-
         </androidx.constraintlayout.widget.ConstraintLayout>
 
     </androidx.cardview.widget.CardView>
diff --git a/res/layout/dream_preview_button.xml b/res/layout/dream_preview_button.xml
new file mode 100644
index 0000000..b347863
--- /dev/null
+++ b/res/layout/dream_preview_button.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+
+<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/dream_preview_button"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:text="@string/dream_preview_button_title"
+    android:textAllCaps="false"
+    android:textColor="?androidprv:attr/textColorOnAccent"
+    android:theme="@style/Theme.CollapsingToolbar.Settings"
+    android:layout_marginBottom="16dp"
+    android:layout_gravity="bottom|center"
+    app:backgroundTint="?androidprv:attr/colorAccentPrimaryVariant"
+    app:iconTint="?androidprv:attr/textColorOnAccent"
+    app:icon="@drawable/dream_preview_icon"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e325c3a..7fa32dc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5236,6 +5236,8 @@
     <string name="accessibility_text_reading_preview_mail_from">From: bill@email.com</string>
     <!-- Content for the mail content of the accessibility text reading preview. [CHAR LIMIT=NONE] -->
     <string name="accessibility_text_reading_preview_mail_content">Good morning! Following up on our last conversation, I’d like to check in on the progress of your time machine development plan. Will you be able to have a prototype ready to demo at E3 this year?</string>
+    <!-- Title for the reset button of the accessibility text reading page to reset all preferences state. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_text_reading_reset_button_title">Reset</string>
     <!-- Title for the footer text to explain what option accessibility service does. [CHAR LIMIT=35] -->
     <string name="accessibility_screen_option">Options</string>
     <!-- Summary for the accessibility preference to enable screen magnification. [CHAR LIMIT=25] -->
diff --git a/res/xml/accessibility_text_reading_options.xml b/res/xml/accessibility_text_reading_options.xml
index 4bc9317..7d81565 100644
--- a/res/xml/accessibility_text_reading_options.xml
+++ b/res/xml/accessibility_text_reading_options.xml
@@ -21,16 +21,44 @@
     android:persistent="false"
     android:title="@string/accessibility_text_reading_options_title">
 
+    <com.android.settings.accessibility.TextReadingPreviewPreference
+        android:key="preview"
+        android:selectable="false"/>
+
+    <com.android.settings.widget.LabeledSeekBarPreference
+        android:key="font_size"
+        android:selectable="false"
+        android:summary="@string/short_summary_font_size"
+        android:title="@string/title_font_size"
+        settings:iconEnd="@drawable/ic_add_24dp"
+        settings:iconEndContentDescription="@string/font_size_make_larger_desc"
+        settings:iconStart="@drawable/ic_remove_24dp"
+        settings:iconStartContentDescription="@string/font_size_make_smaller_desc"/>
+
+    <com.android.settings.widget.LabeledSeekBarPreference
+        android:key="display_size"
+        android:selectable="false"
+        android:summary="@string/screen_zoom_short_summary"
+        android:title="@string/screen_zoom_title"
+        settings:iconEnd="@drawable/ic_add_24dp"
+        settings:iconEndContentDescription="@string/screen_zoom_make_larger_desc"
+        settings:iconStart="@drawable/ic_remove_24dp"
+        settings:iconStartContentDescription="@string/screen_zoom_make_smaller_desc"/>
+
     <SwitchPreference
         android:key="toggle_force_bold_text"
         android:persistent="false"
         android:title="@string/force_bold_text"
-        settings:keywords="@string/keywords_bold_text"
-        settings:controller="com.android.settings.accessibility.FontWeightAdjustmentPreferenceController"/>
+        settings:keywords="@string/keywords_bold_text" />
 
     <SwitchPreference
         android:key="toggle_high_text_contrast_preference"
         android:persistent="false"
-        android:title="@string/accessibility_toggle_high_text_contrast_preference_title"
-        settings:controller="com.android.settings.accessibility.HighTextContrastPreferenceController"/>
+        android:title="@string/accessibility_toggle_high_text_contrast_preference_title" />
+
+    <com.android.settingslib.widget.LayoutPreference
+        android:key="reset"
+        android:layout="@layout/accessibility_text_reading_reset_button"
+        android:persistent="false"
+        android:selectable="false" />
 </PreferenceScreen>
diff --git a/src/com/android/settings/accessibility/DisplaySizeData.java b/src/com/android/settings/accessibility/DisplaySizeData.java
new file mode 100644
index 0000000..42a8c46
--- /dev/null
+++ b/src/com/android/settings/accessibility/DisplaySizeData.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.Display;
+
+import com.android.settingslib.display.DisplayDensityConfiguration;
+import com.android.settingslib.display.DisplayDensityUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+/**
+ * Data class for storing the configurations related to the display size.
+ */
+class DisplaySizeData extends PreviewSizeData<Integer> {
+    DisplaySizeData(Context context) {
+        super(context);
+
+        final DisplayDensityUtils density = new DisplayDensityUtils(getContext());
+        final int initialIndex = density.getCurrentIndex();
+        if (initialIndex < 0) {
+            // Failed to obtain default density, which means we failed to
+            // connect to the window manager service. Just use the current
+            // density and don't let the user change anything.
+            final Resources resources = getContext().getResources();
+            final int densityDpi = resources.getDisplayMetrics().densityDpi;
+            setDefaultValue(densityDpi);
+            setInitialIndex(0);
+            setValues(Collections.singletonList(densityDpi));
+        } else {
+            setDefaultValue(density.getDefaultDensity());
+            setInitialIndex(initialIndex);
+            setValues(Arrays.stream(density.getValues()).boxed().collect(Collectors.toList()));
+        }
+    }
+
+    @Override
+    void commit(int currentProgress) {
+        final int densityDpi = getValues().get(currentProgress);
+        if (densityDpi == getDefaultValue()) {
+            DisplayDensityConfiguration.clearForcedDisplayDensity(Display.DEFAULT_DISPLAY);
+        } else {
+            DisplayDensityConfiguration.setForcedDisplayDensity(Display.DEFAULT_DISPLAY,
+                    densityDpi);
+        }
+    }
+}
diff --git a/src/com/android/settings/accessibility/FontSizeData.java b/src/com/android/settings/accessibility/FontSizeData.java
new file mode 100644
index 0000000..1d4f6bd
--- /dev/null
+++ b/src/com/android/settings/accessibility/FontSizeData.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static com.android.settings.display.ToggleFontSizePreferenceFragment.fontSizeValueToIndex;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+
+import com.android.settings.R;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Data class for storing the configurations related to the font size.
+ */
+final class FontSizeData extends PreviewSizeData<Float> {
+    private static final float FONT_SCALE_DEF_VALUE = 1.0f;
+
+    FontSizeData(Context context) {
+        super(context);
+
+        final Resources resources = getContext().getResources();
+        final ContentResolver resolver = getContext().getContentResolver();
+        final List<String> strEntryValues =
+                Arrays.asList(resources.getStringArray(R.array.entryvalues_font_size));
+        setDefaultValue(FONT_SCALE_DEF_VALUE);
+        final float currentScale =
+                Settings.System.getFloat(resolver, Settings.System.FONT_SCALE, getDefaultValue());
+        setInitialIndex(fontSizeValueToIndex(currentScale, strEntryValues.toArray(new String[0])));
+        setValues(strEntryValues.stream().map(Float::valueOf).collect(Collectors.toList()));
+    }
+
+    @Override
+    void commit(int currentProgress) {
+        final ContentResolver resolver = getContext().getContentResolver();
+        Settings.System.putFloat(resolver, Settings.System.FONT_SCALE,
+                getValues().get(currentProgress));
+    }
+}
diff --git a/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceController.java b/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceController.java
index b59b3b2..e3c1b9e 100644
--- a/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceController.java
+++ b/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceController.java
@@ -24,7 +24,8 @@
 import com.android.settings.core.TogglePreferenceController;
 
 /** PreferenceController for displaying all text in bold. */
-public class FontWeightAdjustmentPreferenceController extends TogglePreferenceController {
+public class FontWeightAdjustmentPreferenceController extends TogglePreferenceController implements
+        TextReadingResetController.ResetStateListener {
     static final int BOLD_TEXT_ADJUSTMENT =
             FontStyle.FONT_WEIGHT_BOLD - FontStyle.FONT_WEIGHT_NORMAL;
 
@@ -53,4 +54,9 @@
     public int getSliceHighlightMenuRes() {
         return R.string.menu_key_accessibility;
     }
+
+    @Override
+    public void resetState() {
+        setChecked(false);
+    }
 }
diff --git a/src/com/android/settings/accessibility/HighTextContrastPreferenceController.java b/src/com/android/settings/accessibility/HighTextContrastPreferenceController.java
index e98a28c..aad69b9 100644
--- a/src/com/android/settings/accessibility/HighTextContrastPreferenceController.java
+++ b/src/com/android/settings/accessibility/HighTextContrastPreferenceController.java
@@ -22,7 +22,11 @@
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
 
-public class HighTextContrastPreferenceController extends TogglePreferenceController {
+/**
+ * PreferenceController for displaying all text in high contrast style.
+ */
+public class HighTextContrastPreferenceController extends TogglePreferenceController implements
+        TextReadingResetController.ResetStateListener {
 
     public HighTextContrastPreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
@@ -49,4 +53,9 @@
     public int getSliceHighlightMenuRes() {
         return R.string.menu_key_accessibility;
     }
+
+    @Override
+    public void resetState() {
+        setChecked(false);
+    }
 }
diff --git a/src/com/android/settings/accessibility/PreviewSizeData.java b/src/com/android/settings/accessibility/PreviewSizeData.java
new file mode 100644
index 0000000..5d4204e
--- /dev/null
+++ b/src/com/android/settings/accessibility/PreviewSizeData.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Abstract data class for storing and fetching the configurations related to the preview of the
+ * text and reading options.
+ */
+abstract class PreviewSizeData<T extends Number> {
+    private final Context mContext;
+    private int mInitialIndex;
+    private T mDefaultValue;
+    private List<T> mValues;
+
+    PreviewSizeData(@NonNull Context context) {
+        mContext = context;
+    }
+
+    Context getContext() {
+        return mContext;
+    }
+
+    List<T> getValues() {
+        return mValues;
+    }
+
+    void setValues(List<T> values) {
+        mValues = values;
+    }
+
+    T getDefaultValue() {
+        return mDefaultValue;
+    }
+
+    void setDefaultValue(T defaultValue) {
+        mDefaultValue = defaultValue;
+    }
+
+    int getInitialIndex() {
+        return mInitialIndex;
+    }
+
+    void setInitialIndex(int initialIndex) {
+        mInitialIndex = initialIndex;
+    }
+
+    /**
+     * Persists the selected size.
+     */
+    abstract void commit(int currentProgress);
+}
diff --git a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
new file mode 100644
index 0000000..c7dfd61
--- /dev/null
+++ b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import android.content.Context;
+import android.widget.SeekBar;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.widget.LabeledSeekBarPreference;
+
+/**
+ * The controller of {@link LabeledSeekBarPreference} that listens to display size and font size
+ * settings changes and updates preview size threshold smoothly.
+ */
+class PreviewSizeSeekBarController extends BasePreferenceController implements
+        TextReadingResetController.ResetStateListener {
+    private final PreviewSizeData<? extends Number> mSizeData;
+    private boolean mSeekByTouch;
+    private ProgressInteractionListener mInteractionListener;
+    private LabeledSeekBarPreference mSeekBarPreference;
+
+    private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
+            new SeekBar.OnSeekBarChangeListener() {
+                @Override
+                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                    mInteractionListener.notifyPreferenceChanged();
+
+                    if (!mSeekByTouch && mInteractionListener != null) {
+                        mInteractionListener.onProgressChanged();
+                    }
+                }
+
+                @Override
+                public void onStartTrackingTouch(SeekBar seekBar) {
+                    mSeekByTouch = true;
+                }
+
+                @Override
+                public void onStopTrackingTouch(SeekBar seekBar) {
+                    mSeekByTouch = false;
+
+                    if (mInteractionListener != null) {
+                        mInteractionListener.onEndTrackingTouch();
+                    }
+                }
+            };
+
+    PreviewSizeSeekBarController(Context context, String preferenceKey,
+            @NonNull PreviewSizeData<? extends Number> sizeData) {
+        super(context, preferenceKey);
+        mSizeData = sizeData;
+    }
+
+    void setInteractionListener(ProgressInteractionListener interactionListener) {
+        mInteractionListener = interactionListener;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        final int dataSize = mSizeData.getValues().size();
+        final int initialIndex = mSizeData.getInitialIndex();
+        mSeekBarPreference = screen.findPreference(getPreferenceKey());
+        mSeekBarPreference.setMax(dataSize - 1);
+        mSeekBarPreference.setProgress(initialIndex);
+        mSeekBarPreference.setContinuousUpdates(true);
+        mSeekBarPreference.setOnSeekBarChangeListener(mSeekBarChangeListener);
+    }
+
+    @Override
+    public void resetState() {
+        final int defaultProgress = mSizeData.getValues().indexOf(mSizeData.getDefaultValue());
+        mSeekBarPreference.setProgress(defaultProgress);
+    }
+
+    /**
+     * Interface for callbacks when users interact with the seek bar.
+     */
+    interface ProgressInteractionListener {
+
+        /**
+         * Called when the progress is changed.
+         */
+        void notifyPreferenceChanged();
+
+        /**
+         * Called when the progress is changed without tracking touch.
+         */
+        void onProgressChanged();
+
+        /**
+         * Called when the seek bar is end tracking.
+         */
+        void onEndTrackingTouch();
+    }
+}
diff --git a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
index 0e8457b..7dd70af 100644
--- a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
@@ -16,13 +16,21 @@
 
 package com.android.settings.accessibility;
 
+import static com.android.settings.accessibility.TextReadingResetController.ResetStateListener;
+
 import android.app.settings.SettingsEnums;
+import android.content.Context;
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.search.SearchIndexable;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
 /**
  * Accessibility settings for adjusting the system features which are related to the reading. For
  * example, bold text, high contrast text, display size, font size and so on.
@@ -30,6 +38,12 @@
 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
 public class TextReadingPreferenceFragment extends DashboardFragment {
     private static final String TAG = "TextReadingPreferenceFragment";
+    private static final String FONT_SIZE_KEY = "font_size";
+    private static final String DISPLAY_SIZE_KEY = "display_size";
+    private static final String PREVIEW_KEY = "preview";
+    private static final String RESET_KEY = "reset";
+    private static final String BOLD_TEXT_KEY = "toggle_force_bold_text";
+    private static final String HIGHT_TEXT_CONTRAST_KEY = "toggle_high_text_contrast_preference";
 
     @Override
     protected int getPreferenceScreenResId() {
@@ -46,6 +60,44 @@
         return SettingsEnums.ACCESSIBILITY_TEXT_READING_OPTIONS;
     }
 
+    @Override
+    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+        final List<AbstractPreferenceController> controllers = new ArrayList<>();
+        final FontSizeData fontSizeData = new FontSizeData(context);
+        final DisplaySizeData displaySizeData = new DisplaySizeData(context);
+
+        final TextReadingPreviewController previewController = new TextReadingPreviewController(
+                context, PREVIEW_KEY, fontSizeData, displaySizeData);
+        controllers.add(previewController);
+
+        final PreviewSizeSeekBarController fontSizeController = new PreviewSizeSeekBarController(
+                context, FONT_SIZE_KEY, fontSizeData);
+        fontSizeController.setInteractionListener(previewController);
+        controllers.add(fontSizeController);
+
+        final PreviewSizeSeekBarController displaySizeController = new PreviewSizeSeekBarController(
+                context, DISPLAY_SIZE_KEY, displaySizeData);
+        displaySizeController.setInteractionListener(previewController);
+        controllers.add(displaySizeController);
+
+        final FontWeightAdjustmentPreferenceController fontWeightController =
+                new FontWeightAdjustmentPreferenceController(context, BOLD_TEXT_KEY);
+        controllers.add(fontWeightController);
+
+        final HighTextContrastPreferenceController highTextContrastController =
+                new HighTextContrastPreferenceController(context, HIGHT_TEXT_CONTRAST_KEY);
+        controllers.add(highTextContrastController);
+
+        final List<ResetStateListener> resetStateListeners =
+                controllers.stream().filter(c -> c instanceof ResetStateListener).map(
+                        c -> (ResetStateListener) c).collect(Collectors.toList());
+        final TextReadingResetController resetController =
+                new TextReadingResetController(context, RESET_KEY, resetStateListeners);
+        controllers.add(resetController);
+
+        return controllers;
+    }
+
     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider(R.xml.accessibility_text_reading_options);
 }
diff --git a/src/com/android/settings/accessibility/TextReadingPreviewController.java b/src/com/android/settings/accessibility/TextReadingPreviewController.java
new file mode 100644
index 0000000..cef20aa
--- /dev/null
+++ b/src/com/android/settings/accessibility/TextReadingPreviewController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.SystemClock;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.display.PreviewPagerAdapter;
+import com.android.settings.widget.LabeledSeekBarPreference;
+
+import java.util.Objects;
+
+/**
+ * A {@link BasePreferenceController} for controlling the preview pager of the text and reading
+ * options.
+ */
+class TextReadingPreviewController extends BasePreferenceController implements
+        PreviewSizeSeekBarController.ProgressInteractionListener {
+    static final int[] PREVIEW_SAMPLE_RES_IDS = new int[]{
+            R.layout.accessibility_text_reading_preview_app_grid,
+            R.layout.screen_zoom_preview_1,
+            R.layout.accessibility_text_reading_preview_mail_content};
+
+    private static final String PREVIEW_KEY = "preview";
+    private static final String FONT_SIZE_KEY = "font_size";
+    private static final String DISPLAY_SIZE_KEY = "display_size";
+    private static final long MIN_COMMIT_INTERVAL_MS = 800;
+    private static final long CHANGE_BY_SEEKBAR_DELAY_MS = 100;
+    private static final long CHANGE_BY_BUTTON_DELAY_MS = 300;
+    private final FontSizeData mFontSizeData;
+    private final DisplaySizeData mDisplaySizeData;
+    private int mLastFontProgress;
+    private int mLastDisplayProgress;
+    private long mLastCommitTime;
+    private TextReadingPreviewPreference mPreviewPreference;
+    private LabeledSeekBarPreference mFontSizePreference;
+    private LabeledSeekBarPreference mDisplaySizePreference;
+
+    private final Choreographer.FrameCallback mCommit = f -> {
+        tryCommitFontSizeConfig();
+        tryCommitDisplaySizeConfig();
+
+        mLastCommitTime = SystemClock.elapsedRealtime();
+    };
+
+    TextReadingPreviewController(Context context, String preferenceKey,
+            @NonNull FontSizeData fontSizeData, @NonNull DisplaySizeData displaySizeData) {
+        super(context, preferenceKey);
+        mFontSizeData = fontSizeData;
+        mDisplaySizeData = displaySizeData;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        mPreviewPreference = screen.findPreference(PREVIEW_KEY);
+
+        mFontSizePreference = screen.findPreference(FONT_SIZE_KEY);
+        mDisplaySizePreference = screen.findPreference(DISPLAY_SIZE_KEY);
+        Objects.requireNonNull(mFontSizePreference,
+                /* message= */ "Font size preference is null, the preview controller "
+                        + "couldn't get the info");
+        Objects.requireNonNull(mDisplaySizePreference,
+                /* message= */ "Display size preference is null, the preview controller"
+                        + " couldn't get the info");
+
+        mLastFontProgress = mFontSizePreference.getProgress();
+        mLastDisplayProgress = mDisplaySizePreference.getProgress();
+
+        final Configuration origConfig = mContext.getResources().getConfiguration();
+        final boolean isLayoutRtl =
+                origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl,
+                PREVIEW_SAMPLE_RES_IDS, createConfig(origConfig));
+        mPreviewPreference.setPreviewAdapter(pagerAdapter);
+        pagerAdapter.setPreviewLayer(/* newLayerIndex= */ 0,
+                /* currentLayerIndex= */ 0,
+                /* currentFrameIndex= */ 0, /* animate= */ false);
+    }
+
+    @Override
+    public void notifyPreferenceChanged() {
+        final int displayDataSize = mDisplaySizeData.getValues().size();
+        final int fontSizeProgress = mFontSizePreference.getProgress();
+        final int displaySizeProgress = mDisplaySizePreference.getProgress();
+
+        // To be consistent with the
+        // {@link PreviewPagerAdapter#setPreviewLayer(int, int, int, boolean)} behavior,
+        // here also needs the same design. In addition, please also refer to
+        // the {@link #createConfig(Configuration)}.
+        final int pagerIndex = fontSizeProgress * displayDataSize + displaySizeProgress;
+
+        mPreviewPreference.notifyPreviewPagerChanged(pagerIndex);
+    }
+
+    @Override
+    public void onProgressChanged() {
+        postCommitDelayed(CHANGE_BY_BUTTON_DELAY_MS);
+    }
+
+    @Override
+    public void onEndTrackingTouch() {
+        postCommitDelayed(CHANGE_BY_SEEKBAR_DELAY_MS);
+    }
+
+    /**
+     * Avoids the flicker when switching to the previous or next level.
+     *
+     * <p><br>[Flickering problem steps] commit()-> snapshot in framework(old screenshot) ->
+     * app update the preview -> snapshot(old screen) fade out</p>
+     *
+     * <p><br>To prevent flickering problem, we make sure that we update the local preview
+     * first and then we do the commit later. </p>
+     *
+     * <p><br><b>Note:</b> It doesn't matter that we use
+     * Choreographer or main thread handler since the delay time is longer
+     * than 1 frame. Use Choreographer to let developer understand it's a
+     * window update.</p>
+     *
+     * @param commitDelayMs the interval time after a action.
+     */
+    void postCommitDelayed(long commitDelayMs) {
+        if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) {
+            commitDelayMs += MIN_COMMIT_INTERVAL_MS;
+        }
+
+        final Choreographer choreographer = Choreographer.getInstance();
+        choreographer.removeFrameCallback(mCommit);
+        choreographer.postFrameCallbackDelayed(mCommit, commitDelayMs);
+    }
+
+    private void tryCommitFontSizeConfig() {
+        final int fontProgress = mFontSizePreference.getProgress();
+        if (fontProgress != mLastFontProgress) {
+            mFontSizeData.commit(fontProgress);
+            mLastFontProgress = fontProgress;
+        }
+    }
+
+    private void tryCommitDisplaySizeConfig() {
+        final int displayProgress = mDisplaySizePreference.getProgress();
+        if (displayProgress != mLastDisplayProgress) {
+            mDisplaySizeData.commit(displayProgress);
+            mLastDisplayProgress = displayProgress;
+        }
+    }
+
+    private Configuration[] createConfig(Configuration origConfig) {
+        final int fontDataSize = mFontSizeData.getValues().size();
+        final int displayDataSize = mDisplaySizeData.getValues().size();
+        final int totalNum = fontDataSize * displayDataSize;
+        final Configuration[] configurations = new Configuration[totalNum];
+
+        for (int i = 0; i < fontDataSize; ++i) {
+            for (int j = 0; j < displayDataSize; ++j) {
+                final Configuration config = new Configuration(origConfig);
+                config.fontScale = mFontSizeData.getValues().get(i);
+                config.densityDpi = mDisplaySizeData.getValues().get(j);
+
+                configurations[i * displayDataSize + j] = config;
+            }
+        }
+
+        return configurations;
+    }
+}
diff --git a/src/com/android/settings/accessibility/TextReadingPreviewPreference.java b/src/com/android/settings/accessibility/TextReadingPreviewPreference.java
index 1b9cc4b..4b8ca39 100644
--- a/src/com/android/settings/accessibility/TextReadingPreviewPreference.java
+++ b/src/com/android/settings/accessibility/TextReadingPreviewPreference.java
@@ -32,8 +32,9 @@
 /**
  * A {@link Preference} that could show the preview related to the text and reading options.
  */
-final class TextReadingPreviewPreference extends Preference {
+public class TextReadingPreviewPreference extends Preference {
     private int mCurrentItem;
+    private int mLastLayerIndex;
     private PreviewPagerAdapter mPreviewAdapter;
 
     TextReadingPreviewPreference(Context context) {
@@ -41,7 +42,7 @@
         init();
     }
 
-    TextReadingPreviewPreference(Context context, AttributeSet attrs) {
+    public TextReadingPreviewPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
         init();
     }
@@ -120,4 +121,16 @@
     private void init() {
         setLayoutResource(R.layout.accessibility_text_reading_preview);
     }
+
+    void notifyPreviewPagerChanged(int pagerIndex) {
+        Preconditions.checkNotNull(mPreviewAdapter,
+                "Preview adapter is null, you should init the preview adapter first");
+
+        if (pagerIndex != mLastLayerIndex) {
+            mPreviewAdapter.setPreviewLayer(pagerIndex, mLastLayerIndex, getCurrentItem(),
+                    /* animate= */ false);
+        }
+
+        mLastLayerIndex = pagerIndex;
+    }
 }
diff --git a/src/com/android/settings/accessibility/TextReadingResetController.java b/src/com/android/settings/accessibility/TextReadingResetController.java
new file mode 100644
index 0000000..f4752cb
--- /dev/null
+++ b/src/com/android/settings/accessibility/TextReadingResetController.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.util.List;
+
+/**
+ * The controller of the reset button in the text and reading options page.
+ */
+class TextReadingResetController extends BasePreferenceController {
+    private final List<ResetStateListener> mListeners;
+
+    TextReadingResetController(Context context, String preferenceKey,
+            @NonNull List<ResetStateListener> listeners) {
+        super(context, preferenceKey);
+        mListeners = listeners;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        final LayoutPreference layoutPreference = screen.findPreference(getPreferenceKey());
+        final View view = layoutPreference.findViewById(R.id.reset_button);
+        view.setOnClickListener(v -> mListeners.forEach(ResetStateListener::resetState));
+    }
+
+    /**
+     * Interface for resetting to default state.
+     */
+    interface ResetStateListener {
+        /**
+         * Called when the reset button was clicked.
+         */
+        void resetState();
+    }
+}
diff --git a/src/com/android/settings/display/PreviewPagerAdapter.java b/src/com/android/settings/display/PreviewPagerAdapter.java
index 018be32..693d574 100644
--- a/src/com/android/settings/display/PreviewPagerAdapter.java
+++ b/src/com/android/settings/display/PreviewPagerAdapter.java
@@ -117,7 +117,15 @@
         mAnimationEndAction = action;
     }
 
-    void setPreviewLayer(int newLayerIndex, int currentLayerIndex, int currentFrameIndex,
+    /**
+     * Switches the sample layouts for the preview pager.
+     *
+     * @param newLayerIndex the new layer index
+     * @param currentLayerIndex the current layer index
+     * @param currentFrameIndex the current frame index
+     * @param animate whether to enable the animation
+     */
+    public void setPreviewLayer(int newLayerIndex, int currentLayerIndex, int currentFrameIndex,
             final boolean animate) {
         for (FrameLayout previewFrame : mPreviewFrames) {
             if (currentLayerIndex >= 0) {
diff --git a/src/com/android/settings/dream/DreamPickerController.java b/src/com/android/settings/dream/DreamPickerController.java
index e4081ac..6d5463c 100644
--- a/src/com/android/settings/dream/DreamPickerController.java
+++ b/src/com/android/settings/dream/DreamPickerController.java
@@ -19,8 +19,6 @@
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.widget.Button;
 
 import androidx.annotation.Nullable;
 import androidx.preference.Preference;
@@ -46,7 +44,6 @@
     private final DreamBackend mBackend;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private final List<DreamInfo> mDreamInfos;
-    private Button mPreviewButton;
     @Nullable
     private DreamInfo mActiveDream;
     private DreamAdapter mAdapter;
@@ -86,17 +83,6 @@
         recyclerView.setLayoutManager(new AutoFitGridLayoutManager(mContext));
         recyclerView.setHasFixedSize(true);
         recyclerView.setAdapter(mAdapter);
-
-        mPreviewButton = ((LayoutPreference) preference).findViewById(R.id.preview_button);
-        mPreviewButton.setVisibility(View.VISIBLE);
-        mPreviewButton.setOnClickListener(v -> mBackend.preview(mActiveDream));
-        updatePreviewButtonState();
-    }
-
-    private void updatePreviewButtonState() {
-        final boolean hasDream = mActiveDream != null;
-        mPreviewButton.setClickable(hasDream);
-        mPreviewButton.setEnabled(hasDream);
     }
 
     @Nullable
@@ -129,7 +115,6 @@
         public void onItemClicked() {
             mActiveDream = mDreamInfo;
             mBackend.setActiveDream(mDreamInfo.componentName);
-            updatePreviewButtonState();
             mMetricsFeatureProvider.action(
                     mContext,
                     SettingsEnums.ACTION_DREAM_SELECT_TYPE,
diff --git a/src/com/android/settings/dream/DreamSettings.java b/src/com/android/settings/dream/DreamSettings.java
index 7ae364e..2acce07 100644
--- a/src/com/android/settings/dream/DreamSettings.java
+++ b/src/com/android/settings/dream/DreamSettings.java
@@ -23,8 +23,13 @@
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.Button;
 
 import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
@@ -144,6 +149,25 @@
         return controllers;
     }
 
+    @Override
+    public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
+            Bundle bundle) {
+
+        final ViewGroup root = getActivity().findViewById(android.R.id.content);
+        final Button previewButton = (Button) getActivity().getLayoutInflater().inflate(
+                R.layout.dream_preview_button, root, false);
+        root.addView(previewButton);
+
+        final DreamBackend dreamBackend = DreamBackend.getInstance(getContext());
+        previewButton.setOnClickListener(v -> dreamBackend.preview(dreamBackend.getActiveDream()));
+
+        final RecyclerView recyclerView = super.onCreateRecyclerView(inflater, parent, bundle);
+        previewButton.post(() -> {
+            recyclerView.setPadding(0, 0, 0, previewButton.getMeasuredHeight());
+        });
+        return recyclerView;
+    }
+
     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER
             = new BaseSearchIndexProvider(R.xml.dream_fragment_overview) {
 
diff --git a/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java b/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
index c6d1ea0..0b39d6a 100644
--- a/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
+++ b/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
@@ -21,14 +21,13 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.UiccCardInfo;
-import android.telephony.UiccSlotMapping;
 import android.telephony.euicc.EuiccManager;
 import android.util.Log;
 
 import com.android.settings.SidecarFragment;
 import com.android.settings.network.telephony.EuiccOperationSidecar;
 
-import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -95,11 +94,11 @@
 
         // To check whether the esim slot's port is active. If yes, skip setSlotMapping. If no,
         // set this slot+port into setSimSlotMapping.
-        mPort = (port < 0) ? getTargetPortId(removedSubInfo, targetSlot) : port;
+        mPort = (port < 0) ? getTargetPortId(removedSubInfo) : port;
         mRemovedSubInfo = removedSubInfo;
         Log.d(TAG,
-                String.format("set esim into the Slot%d SubId%d:Port%d",
-                        targetSlot, mSubId, mPort));
+                String.format("set esim into the SubId%d Slot%d:Port%d",
+                        mSubId, targetSlot, mPort));
 
         if (mTelephonyManager.isMultiSimEnabled() && removedSubInfo != null
                 && removedSubInfo.isEmbedded()) {
@@ -115,7 +114,7 @@
         }
     }
 
-    private int getTargetPortId(SubscriptionInfo removedSubInfo, int targetSlot) {
+    private int getTargetPortId(SubscriptionInfo removedSubInfo) {
         if (!mTelephonyManager.isMultiSimEnabled() || !isMultipleEnabledProfilesSupported()) {
             // In the 'SS mode' or 'DSDS+no MEP', the port is 0.
             return 0;
@@ -128,20 +127,25 @@
         }
 
         // In DSDS+MEP mode, the removedSubInfo is psim or is null, it means this esim needs
-        // another port in the esim slot.
-        // To find another esim's port and value is from 0.
+        // a new corresponding port in the esim slot.
         // For example:
         // 1) If there is no enabled esim and the user add new esim. This new esim's port is 0.
-        // 2) If there is one enabled esim and the user add new esim. This new esim's port is 1.
+        // 2) If there is one enabled esim in port0 and the user add new esim. This new esim's
+        // port is 1.
+        // 3) If there is one enabled esim in port1 and the user add new esim. This new esim's
+        // port is 0.
+
         int port = 0;
-        Collection<UiccSlotMapping> uiccSlotMappings = mTelephonyManager.getSimSlotMapping();
-        for (UiccSlotMapping uiccSlotMapping :
-                uiccSlotMappings.stream()
-                        .filter(
-                                uiccSlotMapping -> uiccSlotMapping.getPhysicalSlotIndex()
-                                        == targetSlot)
-                        .collect(Collectors.toList())) {
-            if (uiccSlotMapping.getPortIndex() == port) {
+        SubscriptionManager subscriptionManager = getContext().getSystemService(
+                SubscriptionManager.class);
+        List<SubscriptionInfo> activeEsimSubInfos =
+                SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
+                        .stream()
+                        .filter(i -> i.isEmbedded())
+                        .sorted(Comparator.comparingInt(SubscriptionInfo::getPortIndex))
+                        .collect(Collectors.toList());
+        for (SubscriptionInfo subscriptionInfo : activeEsimSubInfos) {
+            if (subscriptionInfo.getPortIndex() == port) {
                 port++;
             }
         }
diff --git a/src/com/android/settings/safetycenter/BiometricsSafetySource.java b/src/com/android/settings/safetycenter/BiometricsSafetySource.java
new file mode 100644
index 0000000..f37ea03
--- /dev/null
+++ b/src/com/android/settings/safetycenter/BiometricsSafetySource.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.safetycenter;
+
+import android.content.Context;
+
+/** Combined Biometrics Safety Source for Safety Center. */
+public final class BiometricsSafetySource {
+
+    public static final String SAFETY_SOURCE_ID = "BiometricsSafetySource";
+
+    private BiometricsSafetySource() {}
+
+    /** Sends biometric safety data to Safety Center. */
+    public static void sendSafetyData(Context context) {
+        if (!SafetyCenterStatusHolder.get().isEnabled(context)) {
+            return;
+        }
+
+        // TODO(b/215517420): Send biometric data to Safety Center if there are biometrics available
+        // on this device.
+    }
+}
diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
new file mode 100644
index 0000000..66001f2
--- /dev/null
+++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.safetycenter;
+
+import android.app.PendingIntent;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceStatus;
+
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.password.ChooseLockGeneric;
+import com.android.settingslib.transition.SettingsTransitionHelper;
+
+/** Lock Screen Safety Source for Safety Center. */
+public final class LockScreenSafetySource {
+
+    public static final String SAFETY_SOURCE_ID = "LockScreenSafetySource";
+
+    private LockScreenSafetySource() {}
+
+    /** Sends lock screen safety data to Safety Center. */
+    public static void sendSafetyData(Context context) {
+        if (!SafetyCenterStatusHolder.get().isEnabled(context)) {
+            return;
+        }
+
+        // TODO(b/215515298): Replace placeholder SafetySourceData with real data.
+        // TODO(b/217409995): Replace SECURITY_ALTERNATIVE with Safety Center metrics category.
+        Intent clickIntent = new SubSettingLauncher(context)
+                .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName())
+                .setSourceMetricsCategory(SettingsEnums.SECURITY_ALTERNATIVE)
+                .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
+                .toIntent();
+        PendingIntent pendingIntent = PendingIntent
+                .getActivity(
+                        context,
+                        0 /* requestCode */,
+                        clickIntent,
+                        PendingIntent.FLAG_IMMUTABLE);
+        SafetySourceData safetySourceData =
+                new SafetySourceData.Builder(SAFETY_SOURCE_ID).setStatus(
+                        new SafetySourceStatus.Builder(
+                                "Lock Screen",
+                                "Lock screen settings",
+                                SafetySourceStatus.STATUS_LEVEL_OK,
+                                pendingIntent).build()
+                ).build();
+
+        SafetyCenterManagerWrapper.get().sendSafetyCenterUpdate(context, safetySourceData);
+    }
+}
diff --git a/src/com/android/settings/safetycenter/SafetyCenterManagerWrapper.java b/src/com/android/settings/safetycenter/SafetyCenterManagerWrapper.java
new file mode 100644
index 0000000..7e47f23
--- /dev/null
+++ b/src/com/android/settings/safetycenter/SafetyCenterManagerWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.safetycenter;
+
+import android.content.Context;
+import android.safetycenter.SafetyCenterManager;
+import android.safetycenter.SafetySourceData;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** A wrapper for the SafetyCenterManager system service. */
+public class SafetyCenterManagerWrapper {
+
+    private static final String TAG = "SafetyCenterManagerWrapper";
+
+    @VisibleForTesting
+    public static SafetyCenterManagerWrapper sInstance;
+
+    private SafetyCenterManagerWrapper() {}
+
+    /** Returns an instance of {@link SafetyCenterManagerWrapper}. */
+    public static SafetyCenterManagerWrapper get() {
+        if (sInstance == null) {
+            sInstance = new SafetyCenterManagerWrapper();
+        }
+        return sInstance;
+    }
+
+    /** Sends updated safety source data to Safety Center. */
+    public void sendSafetyCenterUpdate(Context context, SafetySourceData safetySourceData) {
+        SafetyCenterManager safetyCenterManager =
+                context.getSystemService(SafetyCenterManager.class);
+
+        if (safetyCenterManager == null) {
+            Log.e(TAG, "System service SAFETY_CENTER_SERVICE (SafetyCenterManager) is null");
+            return;
+        }
+
+        try {
+            safetyCenterManager.sendSafetyCenterUpdate(safetySourceData);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to send SafetySourceData", e);
+            return;
+        }
+    }
+}
diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
new file mode 100644
index 0000000..55e8bba
--- /dev/null
+++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.safetycenter;
+
+import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.google.common.collect.ImmutableList;
+
+/** Broadcast receiver for handling requests from Safety Center for fresh data. */
+public class SafetySourceBroadcastReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!SafetyCenterStatusHolder.get().isEnabled(context)) {
+            return;
+        }
+
+        ImmutableList<String> sourceIds =
+                ImmutableList.copyOf(intent.getStringArrayExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS));
+
+        if (sourceIds.contains(LockScreenSafetySource.SAFETY_SOURCE_ID)) {
+            LockScreenSafetySource.sendSafetyData(context);
+        }
+
+        if (sourceIds.contains(BiometricsSafetySource.SAFETY_SOURCE_ID)) {
+            BiometricsSafetySource.sendSafetyData(context);
+        }
+    }
+}
diff --git a/src/com/android/settings/security/ChangeScreenLockPreferenceController.java b/src/com/android/settings/security/ChangeScreenLockPreferenceController.java
index 5439fef..4e54238 100644
--- a/src/com/android/settings/security/ChangeScreenLockPreferenceController.java
+++ b/src/com/android/settings/security/ChangeScreenLockPreferenceController.java
@@ -16,40 +16,32 @@
 
 package com.android.settings.security;
 
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.storage.StorageManager;
 import android.text.TextUtils;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.Utils;
 import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settings.core.SubSettingLauncher;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.password.ChooseLockGeneric;
-import com.android.settings.security.screenlock.ScreenLockSettings;
 import com.android.settings.widget.GearPreference;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import com.android.settingslib.transition.SettingsTransitionHelper;
 
 public class ChangeScreenLockPreferenceController extends AbstractPreferenceController implements
         PreferenceControllerMixin, GearPreference.OnGearClickListener {
 
     private static final String KEY_UNLOCK_SET_OR_CHANGE = "unlock_set_or_change";
 
-    protected final DevicePolicyManager mDPM;
     protected final SettingsPreferenceFragment mHost;
     protected final UserManager mUm;
     protected final LockPatternUtils mLockPatternUtils;
@@ -57,24 +49,26 @@
     protected final int mUserId = UserHandle.myUserId();
     protected final int mProfileChallengeUserId;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
+    private final ScreenLockPreferenceDetailsUtils mScreenLockPreferenceDetailUtils;
 
     protected RestrictedPreference mPreference;
 
     public ChangeScreenLockPreferenceController(Context context, SettingsPreferenceFragment host) {
         super(context);
         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        mDPM = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
         mLockPatternUtils = FeatureFactory.getFactory(context)
                 .getSecurityFeatureProvider()
                 .getLockPatternUtils(context);
         mHost = host;
         mProfileChallengeUserId = Utils.getManagedProfileId(mUm, mUserId);
         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+        mScreenLockPreferenceDetailUtils =
+                new ScreenLockPreferenceDetailsUtils(context, host.getMetricsCategory());
     }
 
     @Override
     public boolean isAvailable() {
-        return mContext.getResources().getBoolean(R.bool.config_show_unlock_set_or_change);
+        return mScreenLockPreferenceDetailUtils.isAvailable();
     }
 
     @Override
@@ -91,7 +85,7 @@
     @Override
     public void updateState(Preference preference) {
         if (mPreference != null && mPreference instanceof GearPreference) {
-            if (mLockPatternUtils.isSecure(mUserId)) {
+            if (mScreenLockPreferenceDetailUtils.shouldShowGearMenu()) {
                 ((GearPreference) mPreference).setOnGearClickListener(this);
             } else {
                 ((GearPreference) mPreference).setOnGearClickListener(null);
@@ -112,10 +106,7 @@
         if (TextUtils.equals(p.getKey(), getPreferenceKey())) {
             mMetricsFeatureProvider.logClickedPreference(p,
                     p.getExtras().getInt(DashboardFragment.CATEGORY));
-            new SubSettingLauncher(mContext)
-                    .setDestination(ScreenLockSettings.class.getName())
-                    .setSourceMetricsCategory(mHost.getMetricsCategory())
-                    .launch();
+            mScreenLockPreferenceDetailUtils.openScreenLockSettings();
         }
     }
 
@@ -124,51 +115,11 @@
         if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
             return super.handlePreferenceTreeClick(preference);
         }
-        // TODO(b/35930129): Remove once existing password can be passed into vold directly.
-        // Currently we need this logic to ensure that the QUIET_MODE is off for any work
-        // profile with unified challenge on FBE-enabled devices. Otherwise, vold would not be
-        // able to complete the operation due to the lack of (old) encryption key.
-        if (mProfileChallengeUserId != UserHandle.USER_NULL
-                && !mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileChallengeUserId)
-                && StorageManager.isFileEncryptedNativeOnly()) {
-            if (Utils.startQuietModeDialogIfNecessary(mContext, mUm, mProfileChallengeUserId)) {
-                return false;
-            }
-        }
-
-        new SubSettingLauncher(mContext)
-                .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName())
-                .setSourceMetricsCategory(mHost.getMetricsCategory())
-                .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
-                .launch();
-        return true;
+        return mScreenLockPreferenceDetailUtils.openChooseLockGenericFragment();
     }
 
     protected void updateSummary(Preference preference, int userId) {
-        if (!mLockPatternUtils.isSecure(userId)) {
-            if (userId == mProfileChallengeUserId
-                    || mLockPatternUtils.isLockScreenDisabled(userId)) {
-                preference.setSummary(R.string.unlock_set_unlock_mode_off);
-            } else {
-                preference.setSummary(R.string.unlock_set_unlock_mode_none);
-            }
-        } else {
-            switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(userId)) {
-                case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
-                    preference.setSummary(R.string.unlock_set_unlock_mode_pattern);
-                    break;
-                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
-                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
-                    preference.setSummary(R.string.unlock_set_unlock_mode_pin);
-                    break;
-                case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
-                case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
-                case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
-                case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
-                    preference.setSummary(R.string.unlock_set_unlock_mode_password);
-                    break;
-            }
-        }
+        preference.setSummary(mScreenLockPreferenceDetailUtils.getSummary(userId));
         mPreference.setEnabled(true);
     }
 
@@ -181,10 +132,7 @@
     void disableIfPasswordQualityManaged(int userId) {
         final RestrictedLockUtils.EnforcedAdmin admin = RestrictedLockUtilsInternal
                 .checkIfPasswordQualityIsSet(mContext, userId);
-        final DevicePolicyManager dpm = (DevicePolicyManager) mContext
-                .getSystemService(Context.DEVICE_POLICY_SERVICE);
-        if (admin != null && dpm.getPasswordQuality(admin.component, userId)
-                == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) {
+        if (mScreenLockPreferenceDetailUtils.isPasswordQualityManaged(userId, admin)) {
             mPreference.setDisabledByAdmin(admin);
         }
     }
diff --git a/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java b/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java
new file mode 100644
index 0000000..abcacf0
--- /dev/null
+++ b/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 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.security;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+
+import androidx.annotation.StringRes;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
+import com.android.settings.security.screenlock.ScreenLockSettings;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.transition.SettingsTransitionHelper;
+
+/**
+ * Utilities for screen lock details shared between Security Settings and Safety Center.
+ */
+public class ScreenLockPreferenceDetailsUtils {
+
+    private final int mUserId = UserHandle.myUserId();
+    private final Context mContext;
+    private final LockPatternUtils mLockPatternUtils;
+    private final int mProfileChallengeUserId;
+    private final UserManager mUm;
+    private final int mSourceMetricsCategory;
+
+    public ScreenLockPreferenceDetailsUtils(Context context, int sourceMetricsCategory) {
+        mContext = context;
+        mUm = context.getSystemService(UserManager.class);
+        mLockPatternUtils = FeatureFactory.getFactory(context)
+                .getSecurityFeatureProvider()
+                .getLockPatternUtils(context);
+        mProfileChallengeUserId = Utils.getManagedProfileId(mUm, mUserId);
+        mSourceMetricsCategory = sourceMetricsCategory;
+    }
+
+    /**
+     * Returns whether the screen lock settings entity should be shown.
+     */
+    public boolean isAvailable() {
+        return mContext.getResources().getBoolean(R.bool.config_show_unlock_set_or_change);
+    }
+
+    /**
+     * Returns the summary of screen lock settings entity.
+     */
+    public String getSummary(int userId) {
+        final Integer summaryResId = getSummaryResId(userId);
+        return summaryResId != null ? mContext.getResources().getString(summaryResId) : null;
+    }
+
+    /**
+     * Returns whether the password quality is managed by device admin.
+     */
+    public boolean isPasswordQualityManaged(int userId, RestrictedLockUtils.EnforcedAdmin admin) {
+        final DevicePolicyManager dpm = (DevicePolicyManager) mContext
+                .getSystemService(Context.DEVICE_POLICY_SERVICE);
+        return admin != null && dpm.getPasswordQuality(admin.component, userId)
+                == DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
+    }
+
+    /**
+     * Returns whether the Gear Menu should be shown.
+     */
+    public boolean shouldShowGearMenu() {
+        return mLockPatternUtils.isSecure(mUserId);
+    }
+
+    /**
+     * Launches the {@link ScreenLockSettings}.
+     */
+    public void openScreenLockSettings() {
+        new SubSettingLauncher(mContext)
+                .setDestination(ScreenLockSettings.class.getName())
+                .setSourceMetricsCategory(mSourceMetricsCategory)
+                .launch();
+    }
+
+    /**
+     * Tries to launch the {@link ChooseLockGenericFragment} if Quiet Mode is not enabled
+     * for managed profile, otherwise shows a dialog to disable the Quiet Mode.
+     *
+     * @return true if the {@link ChooseLockGenericFragment} is launching.
+     */
+    public boolean openChooseLockGenericFragment() {
+        // TODO(b/35930129): Remove once existing password can be passed into vold directly.
+        // Currently we need this logic to ensure that the QUIET_MODE is off for any work
+        // profile with unified challenge on FBE-enabled devices. Otherwise, vold would not be
+        // able to complete the operation due to the lack of (old) encryption key.
+        if (mProfileChallengeUserId != UserHandle.USER_NULL
+                && !mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileChallengeUserId)
+                && StorageManager.isFileEncryptedNativeOnly()) {
+            if (Utils.startQuietModeDialogIfNecessary(mContext, mUm, mProfileChallengeUserId)) {
+                return false;
+            }
+        }
+
+        new SubSettingLauncher(mContext)
+                .setDestination(ChooseLockGenericFragment.class.getName())
+                .setSourceMetricsCategory(mSourceMetricsCategory)
+                .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
+                .launch();
+        return true;
+    }
+
+    @StringRes
+    private Integer getSummaryResId(int userId) {
+        if (!mLockPatternUtils.isSecure(userId)) {
+            if (userId == mProfileChallengeUserId
+                    || mLockPatternUtils.isLockScreenDisabled(userId)) {
+                return R.string.unlock_set_unlock_mode_off;
+            } else {
+                return R.string.unlock_set_unlock_mode_none;
+            }
+        } else {
+            int keyguardStoredPasswordQuality =
+                    mLockPatternUtils.getKeyguardStoredPasswordQuality(userId);
+            switch (keyguardStoredPasswordQuality) {
+                case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+                    return R.string.unlock_set_unlock_mode_pattern;
+                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+                    return R.string.unlock_set_unlock_mode_pin;
+                case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+                case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+                case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+                case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
+                    return R.string.unlock_set_unlock_mode_password;
+                default:
+                    return null;
+            }
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/DisplaySizeDataTest.java b/tests/robotests/src/com/android/settings/accessibility/DisplaySizeDataTest.java
new file mode 100644
index 0000000..fabf123
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/DisplaySizeDataTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Tests for {@link DisplaySizeData}.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class DisplaySizeDataTest {
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private DisplaySizeData mDisplaySizeData;
+
+    @Before
+    public void setUp() {
+        mDisplaySizeData = new DisplaySizeData(mContext);
+    }
+
+    @Ignore("Ignore it since a NPE is happened in ShadowWindowManagerGlobal. (Ref. b/214161063)")
+    @Test
+    public void commit_success() {
+        final int progress = 4;
+
+        mDisplaySizeData.commit(progress);
+        final float density = mContext.getResources().getDisplayMetrics().density;
+
+        assertThat(density).isEqualTo(mDisplaySizeData.getValues().get(progress));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/FontSizeDataTest.java b/tests/robotests/src/com/android/settings/accessibility/FontSizeDataTest.java
new file mode 100644
index 0000000..7e35714
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/FontSizeDataTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Tests for {@link FontSizeData}.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class FontSizeDataTest {
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private FontSizeData mFontSizeData;
+
+    @Before
+    public void setUp() {
+        mFontSizeData = new FontSizeData(mContext);
+    }
+
+    @Test
+    public void commit_success() {
+        final int progress = 3;
+
+        mFontSizeData.commit(progress);
+        final float currentScale =
+                Settings.System.getFloat(mContext.getContentResolver(), Settings.System.FONT_SCALE,
+                        /* def= */ 1.0f);
+
+        assertThat(currentScale).isEqualTo(mFontSizeData.getValues().get(progress));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
new file mode 100644
index 0000000..d33d80e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.widget.LabeledSeekBarPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Tests for {@link PreviewSizeSeekBarController}.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class PreviewSizeSeekBarControllerTest {
+    private static final String FONT_SIZE_KEY = "font_size";
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private PreviewSizeSeekBarController mSeekBarController;
+    private FontSizeData mFontSizeData;
+    private LabeledSeekBarPreference mSeekBarPreference;
+
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mFontSizeData = new FontSizeData(mContext);
+
+        mSeekBarController =
+                new PreviewSizeSeekBarController(mContext, FONT_SIZE_KEY, mFontSizeData);
+
+        mSeekBarPreference = spy(new LabeledSeekBarPreference(mContext, /* attrs= */ null));
+        when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference);
+    }
+
+    @Test
+    public void initMax_matchResult() {
+        when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference);
+
+        mSeekBarController.displayPreference(mPreferenceScreen);
+
+        assertThat(mSeekBarPreference.getMax()).isEqualTo(
+                mFontSizeData.getValues().size() - 1);
+    }
+
+    @Test
+    public void initProgress_matchResult() {
+        when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference);
+
+        mSeekBarController.displayPreference(mPreferenceScreen);
+
+        verify(mSeekBarPreference).setProgress(mFontSizeData.getInitialIndex());
+    }
+
+    @Test
+    public void resetToDefaultState_matchResult() {
+        final int defaultProgress =
+                mFontSizeData.getValues().indexOf(mFontSizeData.getDefaultValue());
+        when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference);
+
+        mSeekBarController.displayPreference(mPreferenceScreen);
+        mSeekBarPreference.setProgress(defaultProgress + 1);
+        mSeekBarController.resetState();
+
+        assertThat(mSeekBarPreference.getProgress()).isEqualTo(defaultProgress);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java
new file mode 100644
index 0000000..b630509
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.display.PreviewPagerAdapter;
+import com.android.settings.widget.LabeledSeekBarPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowChoreographer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link TextReadingPreviewController}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowChoreographer.class)
+public class TextReadingPreviewControllerTest {
+    private static final String PREVIEW_KEY = "preview";
+    private static final String FONT_SIZE_KEY = "font_size";
+    private static final String DISPLAY_SIZE_KEY = "display_size";
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private TextReadingPreviewController mPreviewController;
+    private TextReadingPreviewPreference mPreviewPreference;
+    private LabeledSeekBarPreference mFontSizePreference;
+    private LabeledSeekBarPreference mDisplaySizePreference;
+
+    @Mock
+    private DisplaySizeData mDisplaySizeData;
+
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        final FontSizeData fontSizeData = new FontSizeData(mContext);
+        final List<Integer> displayData = createFakeDisplayData();
+        when(mDisplaySizeData.getValues()).thenReturn(displayData);
+        mPreviewPreference = spy(new TextReadingPreviewPreference(mContext, /* attr= */ null));
+        mPreviewController = new TextReadingPreviewController(mContext, PREVIEW_KEY, fontSizeData,
+                mDisplaySizeData);
+        mFontSizePreference = new LabeledSeekBarPreference(mContext, /* attr= */ null);
+        mDisplaySizePreference = new LabeledSeekBarPreference(mContext, /* attr= */ null);
+    }
+
+    @Test
+    public void initPreviewerAdapter_verifyAction() {
+        when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference);
+        when(mPreferenceScreen.findPreference(FONT_SIZE_KEY)).thenReturn(mFontSizePreference);
+        when(mPreferenceScreen.findPreference(DISPLAY_SIZE_KEY)).thenReturn(mDisplaySizePreference);
+
+        mPreviewController.displayPreference(mPreferenceScreen);
+
+        verify(mPreviewPreference).setPreviewAdapter(any(PreviewPagerAdapter.class));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void initPreviewerAdapterWithoutDisplaySizePreference_throwNPE() {
+        when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference);
+        when(mPreferenceScreen.findPreference(DISPLAY_SIZE_KEY)).thenReturn(mDisplaySizePreference);
+
+        mPreviewController.displayPreference(mPreferenceScreen);
+
+        verify(mPreviewPreference).setPreviewAdapter(any(PreviewPagerAdapter.class));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void initPreviewerAdapterWithoutFontSizePreference_throwNPE() {
+        when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference);
+        when(mPreferenceScreen.findPreference(FONT_SIZE_KEY)).thenReturn(mFontSizePreference);
+
+        mPreviewController.displayPreference(mPreferenceScreen);
+
+        verify(mPreviewPreference).setPreviewAdapter(any(PreviewPagerAdapter.class));
+    }
+
+    private List<Integer> createFakeDisplayData() {
+        final List<Integer> list = new ArrayList<>();
+        list.add(1);
+        list.add(2);
+        list.add(3);
+        list.add(4);
+
+        return list;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java
index 6b9395a..3dc82da 100644
--- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java
@@ -16,8 +16,16 @@
 
 package com.android.settings.accessibility;
 
+import static com.android.settings.accessibility.TextReadingPreviewController.PREVIEW_SAMPLE_RES_IDS;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.view.LayoutInflater;
@@ -50,12 +58,11 @@
     @Before
     public void setUp() {
         final Context context = ApplicationProvider.getApplicationContext();
-        final int[] sampleResIds = new int[]{1, 2, 3, 4, 5, 6};
-        final Configuration[] configurations = createConfigurations(6);
+        final Configuration[] configurations = createConfigurations(PREVIEW_SAMPLE_RES_IDS.length);
         mTextReadingPreviewPreference = new TextReadingPreviewPreference(context);
         mPreviewPagerAdapter =
-                new PreviewPagerAdapter(context, /* isLayoutRtl= */ false, sampleResIds,
-                        configurations);
+                spy(new PreviewPagerAdapter(context, /* isLayoutRtl= */ false,
+                        PREVIEW_SAMPLE_RES_IDS, configurations));
         final LayoutInflater inflater = LayoutInflater.from(context);
         final View view =
                 inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(),
@@ -87,7 +94,7 @@
 
     @Test
     public void setCurrentItem_success() {
-        final int currentItem = 3;
+        final int currentItem = 1;
         mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter);
         mTextReadingPreviewPreference.onBindViewHolder(mHolder);
 
@@ -104,6 +111,24 @@
         mTextReadingPreviewPreference.setCurrentItem(currentItem);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void updatePagerWithoutPreviewAdapter_throwNPE() {
+        final int index = 1;
+
+        mTextReadingPreviewPreference.notifyPreviewPagerChanged(index);
+    }
+
+    @Test
+    public void notifyPreviewPager_setPreviewLayer() {
+        final int index = 2;
+        mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter);
+        mTextReadingPreviewPreference.onBindViewHolder(mHolder);
+
+        mTextReadingPreviewPreference.notifyPreviewPagerChanged(index);
+
+        verify(mPreviewPagerAdapter).setPreviewLayer(eq(index), anyInt(), anyInt(), anyBoolean());
+    }
+
     private static Configuration[] createConfigurations(int count) {
         final Configuration[] configurations = new Configuration[count];
         for (int i = 0; i < count; i++) {
diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingResetControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingResetControllerTest.java
new file mode 100644
index 0000000..2ae8e24
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingResetControllerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static com.android.settings.accessibility.TextReadingResetController.ResetStateListener;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.LayoutPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link TextReadingResetController}.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class TextReadingResetControllerTest {
+    private static final String TEST_KEY = "test";
+    private static final String RESET_KEY = "reset";
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private final View mResetView = new View(mContext);
+    private final List<ResetStateListener> mListeners = new ArrayList<>();
+    private TextReadingResetController mResetController;
+    private TestPreferenceController mPreferenceController;
+
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+
+    @Mock
+    private LayoutPreference mLayoutPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mPreferenceController = new TestPreferenceController(mContext, TEST_KEY);
+        mListeners.add(mPreferenceController);
+        mResetController = new TextReadingResetController(mContext, RESET_KEY, mListeners);
+    }
+
+    @Test
+    public void setClickListener_success() {
+        setupResetButton();
+
+        mResetController.displayPreference(mPreferenceScreen);
+
+        assertThat(mResetView.hasOnClickListeners()).isTrue();
+    }
+
+    @Test
+    public void triggerResetState_success() {
+        setupResetButton();
+
+        mResetController.displayPreference(mPreferenceScreen);
+        mResetView.callOnClick();
+
+        assertThat(mPreferenceController.isReset()).isTrue();
+    }
+
+    private void setupResetButton() {
+        when(mPreferenceScreen.findPreference(RESET_KEY)).thenReturn(mLayoutPreference);
+        when(mLayoutPreference.findViewById(R.id.reset_button)).thenReturn(mResetView);
+    }
+
+    private static class TestPreferenceController extends BasePreferenceController implements
+            ResetStateListener {
+        private boolean mIsReset = false;
+
+        TestPreferenceController(Context context, String preferenceKey) {
+            super(context, preferenceKey);
+        }
+
+        @Override
+        public void resetState() {
+            mIsReset = true;
+        }
+
+        @Override
+        public int getAvailabilityStatus() {
+            return AVAILABLE;
+        }
+
+        boolean isReset() {
+            return mIsReset;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java b/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java
index 043fc55..401ffe0 100644
--- a/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java
+++ b/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java
@@ -19,12 +19,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.widget.Button;
 
 import androidx.preference.PreferenceScreen;
 import androidx.recyclerview.widget.RecyclerView;
@@ -93,19 +90,4 @@
         RecyclerView view = mPreference.findViewById(R.id.dream_list);
         assertThat(view.getAdapter().getItemCount()).isEqualTo(1);
     }
-
-    @Test
-    public void testPreviewButton() {
-        final DreamInfo mockDreamInfo = new DreamInfo();
-        mockDreamInfo.componentName = new ComponentName("package", "class");
-        mockDreamInfo.isActive = true;
-
-        when(mBackend.getDreamInfos()).thenReturn(Collections.singletonList(mockDreamInfo));
-        final DreamPickerController controller = buildController();
-        controller.updateState(mPreference);
-
-        Button view = mPreference.findViewById(R.id.preview_button);
-        view.performClick();
-        verify(mBackend).preview(mockDreamInfo);
-    }
 }
diff --git a/tests/robotests/src/com/android/settings/security/ChangeScreenLockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/ChangeScreenLockPreferenceControllerTest.java
index bf9606d..60df553 100644
--- a/tests/robotests/src/com/android/settings/security/ChangeScreenLockPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/ChangeScreenLockPreferenceControllerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -35,6 +36,7 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.shadow.ShadowUtils;
 import com.android.settings.widget.GearPreference;
@@ -54,6 +56,8 @@
 @Ignore
 public class ChangeScreenLockPreferenceControllerTest {
 
+    private static final int METRICS_CATEGORY = 1;
+
     @Mock
     private LockPatternUtils mLockPatternUtils;
     @Mock
@@ -80,7 +84,9 @@
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
                 .thenReturn(mDevicePolicyManager);
-        mController = new ChangeScreenLockPreferenceController(mContext, null  /* Host */ );
+        final SettingsPreferenceFragment host = mock(SettingsPreferenceFragment.class);
+        when(host.getMetricsCategory()).thenReturn(METRICS_CATEGORY);
+        mController = new ChangeScreenLockPreferenceController(mContext, host);
     }
 
     @Test
diff --git a/tests/unit/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceControllerTest.java
index 7f4048d..e3d2408 100644
--- a/tests/unit/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceControllerTest.java
@@ -31,6 +31,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+/**
+ * Tests for {@link FontWeightAdjustmentPreferenceController}.
+ */
 @RunWith(AndroidJUnit4.class)
 public class FontWeightAdjustmentPreferenceControllerTest {
     private static final int ON = FontWeightAdjustmentPreferenceController.BOLD_TEXT_ADJUSTMENT;
@@ -91,4 +94,14 @@
         assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.FONT_WEIGHT_ADJUSTMENT, OFF)).isEqualTo(OFF);
     }
+
+    @Test
+    public void resetState_shouldDisableBoldText() {
+        mController.setChecked(true);
+
+        mController.resetState();
+
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.FONT_WEIGHT_ADJUSTMENT, OFF)).isEqualTo(OFF);
+    }
 }
diff --git a/tests/unit/src/com/android/settings/accessibility/HighTextContrastPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/HighTextContrastPreferenceControllerTest.java
index 6250fef..1ada051 100644
--- a/tests/unit/src/com/android/settings/accessibility/HighTextContrastPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/accessibility/HighTextContrastPreferenceControllerTest.java
@@ -31,6 +31,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+/**
+ * Tests for {@link HighTextContrastPreferenceController}.
+ */
 @RunWith(AndroidJUnit4.class)
 public class HighTextContrastPreferenceControllerTest {
 
@@ -93,4 +96,14 @@
         assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, UNKNOWN)).isEqualTo(OFF);
     }
+
+    @Test
+    public void resetState_shouldDisableTextContrast() {
+        mController.setChecked(true);
+
+        mController.resetState();
+
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, UNKNOWN)).isEqualTo(OFF);
+    }
 }
diff --git a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java
new file mode 100644
index 0000000..f53d336
--- /dev/null
+++ b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 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.safetycenter;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class BiometricsSafetySourceTest {
+
+    private Context mApplicationContext;
+
+    @Mock
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+
+    @Mock
+    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mApplicationContext = ApplicationProvider.getApplicationContext();
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
+        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+    }
+
+    @After
+    public void tearDown() {
+        SafetyCenterManagerWrapper.sInstance = null;
+        SafetyCenterStatusHolder.sInstance = null;
+    }
+
+    @Test
+    public void sendSafetyData_whenSafetyCenterIsDisabled_sendsNoData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(false);
+
+        BiometricsSafetySource.sendSafetyData(mApplicationContext);
+
+        verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
+    }
+
+    @Test
+    // TODO(b/215517420): Adapt this test when method is implemented.
+    public void sendSafetyData_whenSafetyCenterIsEnabled_sendsNoData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+
+        BiometricsSafetySource.sendSafetyData(mApplicationContext);
+
+        verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
+    }
+}
diff --git a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
new file mode 100644
index 0000000..052f981
--- /dev/null
+++ b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 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.safetycenter;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceStatus;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.password.ChooseLockGeneric;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class LockScreenSafetySourceTest {
+
+    private Context mApplicationContext;
+
+    @Mock
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+
+    @Mock
+    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mApplicationContext = ApplicationProvider.getApplicationContext();
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
+        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+    }
+
+    @After
+    public void tearDown() {
+        SafetyCenterManagerWrapper.sInstance = null;
+        SafetyCenterStatusHolder.sInstance = null;
+    }
+
+    @Test
+    public void sendSafetyData_whenSafetyCenterIsDisabled_sendsNoData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(false);
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext);
+
+        verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
+    }
+
+    @Test
+    public void sendSafetyData_whenSafetyCenterIsEnabled_sendsPlaceholderData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext);
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture());
+        SafetySourceData safetySourceData = captor.getValue();
+        SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+        assertThat(safetySourceData.getId()).isEqualTo(LockScreenSafetySource.SAFETY_SOURCE_ID);
+        assertThat(safetySourceStatus.getTitle().toString()).isEqualTo("Lock Screen");
+        assertThat(safetySourceStatus.getSummary().toString())
+                .isEqualTo("Lock screen settings");
+        assertThat(safetySourceStatus.getStatusLevel())
+                .isEqualTo(SafetySourceStatus.STATUS_LEVEL_OK);
+        assertThat(safetySourceStatus.getPendingIntent()).isNotNull();
+        assertThat(safetySourceStatus.getPendingIntent().getIntent().getStringExtra(
+                SettingsActivity.EXTRA_SHOW_FRAGMENT))
+                .isEqualTo(ChooseLockGeneric.ChooseLockGenericFragment.class.getName());
+    }
+}
diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
new file mode 100644
index 0000000..581286b
--- /dev/null
+++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 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.safetycenter;
+
+import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.safetycenter.SafetySourceData;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SafetySourceBroadcastReceiverTest {
+
+    private Context mApplicationContext;
+
+    @Mock
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+
+    @Mock
+    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mApplicationContext = ApplicationProvider.getApplicationContext();
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
+        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+    }
+
+    @After
+    public void tearDown() {
+        SafetyCenterManagerWrapper.sInstance = null;
+        SafetyCenterStatusHolder.sInstance = null;
+    }
+
+    @Test
+    public void sendSafetyData_whenSafetyCenterIsDisabled_sendsNoData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(false);
+        Intent intent =
+                new Intent().putExtra(
+                        EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+                        new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID });
+
+        new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+
+        verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
+    }
+
+    @Test
+    public void sendSafetyData_whenSafetyCenterIsEnabled_withNoSourceIds_sendsNoData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        Intent intent = new Intent().putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, new String[]{});
+
+        new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+
+        verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
+    }
+
+    @Test
+    public void sendSafetyData_withLockscreenSourceId_sendsLockscreenData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        Intent intent =
+                new Intent().putExtra(
+                        EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+                        new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID });
+
+        new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper, times(1))
+                .sendSafetyCenterUpdate(any(), captor.capture());
+        SafetySourceData safetySourceData = captor.getValue();
+
+        assertThat(safetySourceData.getId()).isEqualTo(LockScreenSafetySource.SAFETY_SOURCE_ID);
+    }
+
+    @Test
+    public void sendSafetyData_withBiometricsSourceId_sendsBiometricData() {
+        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        Intent intent =
+                new Intent().putExtra(
+                        EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+                        new String[]{ BiometricsSafetySource.SAFETY_SOURCE_ID });
+
+        new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+
+        // TODO(b/215517420): Update this test when BiometricSafetySource is implemented.
+        verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
+    }
+}
diff --git a/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java b/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java
new file mode 100644
index 0000000..3d13b48
--- /dev/null
+++ b/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2022 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.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.SettingsActivity;
+import com.android.settings.password.ChooseLockGeneric;
+import com.android.settings.security.screenlock.ScreenLockSettings;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.ResourcesUtils;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class ScreenLockPreferenceDetailsUtilsTest {
+
+    private static final int SOURCE_METRICS_CATEGORY = 10;
+    private static final int USER_ID = 11;
+
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private StorageManager mStorageManager;
+
+    private Context mContext;
+
+    private ScreenLockPreferenceDetailsUtils mScreenLockPreferenceDetailsUtils;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+        when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
+                .thenReturn(mDevicePolicyManager);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        doNothing().when(mContext).startActivity(any());
+        when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[] {});
+
+        final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+        when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext))
+                .thenReturn(mLockPatternUtils);
+
+        mScreenLockPreferenceDetailsUtils =
+                new ScreenLockPreferenceDetailsUtils(mContext, SOURCE_METRICS_CATEGORY);
+    }
+
+    @Test
+    public void isAvailable_whenEnabled_shouldReturnTrue() {
+        whenConfigShowUnlockSetOrChangeIsEnabled(true);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_whenDisabled_shouldReturnFalse() {
+        whenConfigShowUnlockSetOrChangeIsEnabled(false);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void getSummary_unsecureAndDisabledPattern_shouldReturnUnlockModeOff() {
+        final String summary = prepareString("unlock_set_unlock_mode_off", "unlockModeOff");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(false);
+        when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_unsecurePattern_shouldReturnUnlockModeNone() {
+        final String summary =
+                prepareString("unlock_set_unlock_mode_none", "unlockModeNone");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(false);
+        when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_passwordQualitySomething_shouldUnlockModePattern() {
+        final String summary =
+                prepareString("unlock_set_unlock_mode_pattern", "unlockModePattern");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_passwordQualityNumeric_shouldUnlockModePin() {
+        final String summary =
+                prepareString("unlock_set_unlock_mode_pin", "unlockModePin");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_passwordQualityNumericComplex_shouldUnlockModePin() {
+        final String summary = prepareString("unlock_set_unlock_mode_pin", "unlockModePin");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_passwordQualityAlphabetic_shouldUnlockModePassword() {
+        final String summary =
+                prepareString("unlock_set_unlock_mode_password", "unlockModePassword");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_passwordQualityAlphanumeric_shouldUnlockModePassword() {
+        final String summary =
+                prepareString("unlock_set_unlock_mode_password", "unlockModePassword");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_passwordQualityComplex_shouldUnlockModePassword() {
+        final String summary =
+                prepareString("unlock_set_unlock_mode_password", "unlockModePassword");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_passwordQualityManaged_shouldUnlockModePassword() {
+        final String summary =
+                prepareString("unlock_set_unlock_mode_password", "unlockModePassword");
+
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_MANAGED);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+    }
+
+
+    @Test
+    public void getSummary_unsupportedPasswordQuality_shouldReturnNull() {
+        when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+
+        assertNull(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID));
+    }
+
+    @Test
+    public void isPasswordQualityManaged_withoutAdmin_shouldReturnFalse() {
+        final RestrictedLockUtils.EnforcedAdmin admin = null;
+
+        assertThat(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(USER_ID, admin))
+                .isFalse();
+    }
+
+    @Test
+    public void isPasswordQualityManaged_passwordQualityIsManaged_shouldReturnTrue() {
+        final RestrictedLockUtils.EnforcedAdmin admin = new RestrictedLockUtils.EnforcedAdmin();
+
+        when(mDevicePolicyManager.getPasswordQuality(admin.component, USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_MANAGED);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(USER_ID, admin))
+                .isTrue();
+    }
+
+    @Test
+    public void isPasswordQualityManaged_passwordQualityIsNotManaged_shouldReturnFalse() {
+        final RestrictedLockUtils.EnforcedAdmin admin = new RestrictedLockUtils.EnforcedAdmin();
+
+        when(mDevicePolicyManager.getPasswordQuality(admin.component, USER_ID))
+                .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(USER_ID, admin))
+                .isFalse();
+    }
+
+    @Test
+    public void shouldShowGearMenu_patternIsSecure_shouldReturnTrue() {
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.shouldShowGearMenu()).isTrue();
+    }
+
+    @Test
+    public void shouldShowGearMenu_patternIsNotSecure_shouldReturnFalse() {
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.shouldShowGearMenu()).isFalse();
+    }
+
+    @Test
+    public void openScreenLockSettings_shouldSendIntent() {
+        mScreenLockPreferenceDetailsUtils.openScreenLockSettings();
+
+        assertFragmentLaunchRequested(ScreenLockSettings.class.getName());
+    }
+
+    @Test
+    public void openChooseLockGenericFragment_noQuietMode_shouldSendIntent_shouldReturnTrue() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.openChooseLockGenericFragment()).isTrue();
+        assertFragmentLaunchRequested(ChooseLockGeneric.ChooseLockGenericFragment.class.getName());
+    }
+
+    private void whenConfigShowUnlockSetOrChangeIsEnabled(boolean enabled) {
+        final int resId = ResourcesUtils.getResourcesId(
+                ApplicationProvider.getApplicationContext(), "bool",
+                "config_show_unlock_set_or_change");
+        when(mResources.getBoolean(resId)).thenReturn(enabled);
+    }
+
+    private String prepareString(String stringResName, String string) {
+        final int stringResId = ResourcesUtils.getResourcesId(
+                ApplicationProvider.getApplicationContext(), "string", stringResName);
+        when(mResources.getString(stringResId)).thenReturn(string);
+        return string;
+    }
+
+    private void assertFragmentLaunchRequested(String fragmentClassName) {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(intentCaptor.capture());
+
+        Intent intent = intentCaptor.getValue();
+        assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+                .isEqualTo(fragmentClassName);
+        assertThat(intent.getIntExtra(
+                MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, -1 /* defaultValue */))
+                .isEqualTo(SOURCE_METRICS_CATEGORY);
+    }
+}