[automerger skipped] Import translations. DO NOT MERGE
am: 97339e3d23 -s ours
am skip reason: subject contains skip directive

Change-Id: Ic6a090c4de07544adb5907af2c41928a21c9224e
diff --git a/Android.mk b/Android.mk
index 9108a76..39b7790 100644
--- a/Android.mk
+++ b/Android.mk
@@ -28,16 +28,19 @@
 
   LOCAL_USE_AAPT2 := true
 
-  LOCAL_JAVA_LIBRARIES += android.car
+  LOCAL_JAVA_LIBRARIES := \
+      android.car \
+      telephony-common
 
   LOCAL_STATIC_ANDROID_LIBRARIES := \
-      android-support-car \
-      android-support-v7-preference \
-      android-support-v14-preference \
-      android-arch-lifecycle-extensions \
-      car-list \
+      androidx.car_car \
+      androidx.lifecycle_lifecycle-common-java8 \
+      androidx.lifecycle_lifecycle-extensions \
+      androidx.preference_preference \
+      androidx-constraintlayout_constraintlayout \
+      car-apps-common \
       car-settings-lib \
-      setup-wizard-lib-gingerbread-compat \
+      car-setup-wizard-lib-utils \
       SettingsLib
 
   LOCAL_RESOURCE_DIR := \
@@ -53,7 +56,10 @@
 
   LOCAL_DEX_PREOPT := false
 
-  LOCAL_STATIC_JAVA_LIBRARIES += jsr305
+  LOCAL_STATIC_JAVA_LIBRARIES := \
+      android.car.userlib \
+      androidx-constraintlayout_constraintlayout-solver \
+      jsr305
 
   LOCAL_DX_FLAGS := --multi-dex
 
@@ -66,6 +72,67 @@
   include $(BUILD_PACKAGE)
 endif
 
+###################################################################################
+# Duplicate of CarSettings which includes testing only resources for Robolectric #
+###################################################################################
+include $(CLEAR_VARS)
+
+# To avoid build errors, build empty package for non-platform builds
+# (for example, projected). See b/30064991
+ifeq (,$(TARGET_BUILD_APPS))
+  LOCAL_PACKAGE_NAME := CarSettingsForTesting
+  LOCAL_PRIVATE_PLATFORM_APIS := true
+
+  LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+  LOCAL_USE_AAPT2 := true
+
+  LOCAL_JAVA_LIBRARIES := \
+      android.car \
+      telephony-common
+
+  LOCAL_STATIC_ANDROID_LIBRARIES := \
+      androidx.car_car \
+      androidx.lifecycle_lifecycle-common-java8 \
+      androidx.lifecycle_lifecycle-extensions \
+      androidx.preference_preference \
+      androidx-constraintlayout_constraintlayout \
+      car-apps-common \
+      car-settings-lib \
+      car-setup-wizard-lib-utils \
+      SettingsLib
+
+  LOCAL_RESOURCE_DIR := \
+      $(LOCAL_PATH)/res \
+      $(LOCAL_PATH)/tests/robotests/res
+
+  LOCAL_CERTIFICATE := platform
+
+  LOCAL_MODULE_TAGS := optional
+
+  LOCAL_PROGUARD_ENABLED := disabled
+
+  LOCAL_PRIVILEGED_MODULE := true
+
+  LOCAL_DEX_PREOPT := false
+
+  LOCAL_STATIC_JAVA_LIBRARIES := \
+      android.car.userlib \
+      androidx-constraintlayout_constraintlayout-solver \
+      jsr305
+
+  LOCAL_DX_FLAGS := --multi-dex
+
+  ifdef DISABLE_AOSP_PHONE_SETTING
+    ifeq ($(DISABLE_AOSP_PHONE_SETTING),true)
+      # This will hide AOSP phone setting.
+      LOCAL_OVERRIDES_PACKAGES := Settings
+    endif
+  endif
+  include $(BUILD_PACKAGE)
+endif
+###################################################################################
+
 # Use the following include to make our test apk.
 ifeq (,$(ONE_SHOT_MAKEFILE))
 include $(call first-makefiles-under, $(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a92c87b..2ef39a1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -18,6 +18,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.car.settings"
+    coreApp="true"
     android:sharedUserId="android.uid.system"
     android:versionCode="1"
     android:versionName="1.0">
@@ -31,6 +32,7 @@
     <uses-permission android:name="android.permission.BACKUP"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA"/>
     <uses-permission android:name="android.permission.DELETE_CACHE_FILES"/>
@@ -49,18 +51,25 @@
     <uses-permission android:name="android.permission.REBOOT"/>
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
     <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"/>
     <uses-permission android:name="android.permission.SET_TIME"/>
     <uses-permission android:name="android.permission.SET_TIME_ZONE"/>
     <uses-permission android:name="android.permission.START_FOREGROUND"/>
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
     <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE"/>
     <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
+    <permission android:name="com.android.car.settings.SET_INITIAL_LOCK"
+        android:protectionLevel="signature|setup"/>
+
     <application
         android:icon="@drawable/ic_launcher_settings"
         android:theme="@style/CarSettingTheme"
         android:label="@string/settings_label"
+        android:requiredForAllUsers="true"
+        android:directBootAware="true"
         android:supportsRtl="true">
 
         <activity
@@ -69,54 +78,279 @@
             android:launchMode="singleTask"
             android:windowSoftInputMode="adjustPan"
             android:exported="true">
-            <!-- Set priority high enough to trump the phone setting app -->
-            <!-- TODO: once phone setting is removed from car system image, set priority to 1 -->
-            <intent-filter android:priority="10">
-                <action android:name="android.intent.action.MAIN" />
+            <!-- Keep the order of intents same as .common.FragmentResolver -->
+            <intent-filter android:priority="1">
                 <action android:name="android.settings.SETTINGS" />
                 <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.NIGHT_DISPLAY_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.DISPLAY_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.SOUND_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.net.wifi.PICK_WIFI_NETWORK" />
+                <action android:name="android.settings.WIFI_SETTINGS" />
+                <action android:name="android.settings.WIRELESS_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.WIFI_IP_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.DATA_USAGE_SETTINGS" />
+                <action android:name="android.settings.MOBILE_DATA_USAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.DATA_ROAMING_SETTINGS" />
+                <action android:name="android.settings.NETWORK_OPERATOR_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.BLUETOOTH_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.LOCATION_SCANNING_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.APPLICATION_SETTINGS" />
+                <action android:name="android.settings.MANAGE_APPLICATIONS_SETTINGS" />
+                <action android:name="android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.APPLICATION_DETAILS_SETTINGS" />
+                <action android:name="android.settings.NOTIFICATION_SETTINGS" />
+                <action android:name="android.settings.CHANNEL_NOTIFICATION_SETTINGS" />
+                <action android:name="android.settings.APP_NOTIFICATION_SETTINGS" />
+                <data android:scheme="package" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.MANAGE_DEFAULT_APPS_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.VOICE_INPUT_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.action.MANAGE_WRITE_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.USAGE_ACCESS_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.DATE_SETTINGS" />
+                <action android:name="android.intent.action.QUICK_CLOCK" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.USER_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.ADD_ACCOUNT_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.SYNC_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.INTERNAL_STORAGE_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.LOCALE_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.REQUEST_SET_AUTOFILL_SERVICE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="package" />
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.INPUT_METHOD_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+
+            <intent-filter android:priority="100">
+                <action android:name="android.settings.DEVICE_INFO_SETTINGS" />
+                <action android:name="android.settings.DEVICE_NAME" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
+
+        <!-- Alias for launcher activity only, as this belongs to each profile. -->
+        <!-- TODO: once phone setting is removed from car system image, set priority to 1 -->
+        <activity-alias android:name="Settings"
+                        android:label="@string/settings_label"
+                        android:launchMode="singleTask"
+                        android:targetActivity=".common.CarSettingActivity">
+            <intent-filter android:priority="10">
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
             <meta-data android:name="distractionOptimized" android:value="true"/>
-        </activity>
+        </activity-alias>
 
         <activity android:name=".bluetooth.BluetoothPairingDialog"
                   android:excludeFromRecents="true"
                   android:windowSoftInputMode="stateVisible|adjustResize"
+                  android:taskAffinity="car.settings.bluetooth"
+                  android:launchMode="singleTask"
                   android:theme="@*android:style/Theme.DeviceDefault.Settings.Dialog.NoActionBar">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
+
+        <activity android:name=".bluetooth.BluetoothDevicePickerActivity"
+                  android:label="@string/bluetooth_device_picker"
+                  android:configChanges="orientation|keyboardHidden|screenSize"
+                  android:clearTaskOnLaunch="true">
+            <intent-filter>
+                <action android:name="android.bluetooth.devicepicker.action.LAUNCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
 
         <activity android:name=".accounts.AddAccountActivity"
                   android:theme="@android:style/Theme.Translucent.NoTitleBar"
-                  android:configChanges="orientation|keyboardHidden|screenSize">
-            <intent-filter>
-                <action android:name="android.car.settings.ADD_ACCOUNT_SETTINGS" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
+                  android:configChanges="orientation|keyboardHidden|screenSize"/>
 
         <activity android:name=".security.SettingsScreenLockActivity"
                   android:configChanges="orientation|keyboardHidden|screenSize"
                   android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.car.settings.SCREEN_LOCK_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="distractionOptimized" android:value="true"/>
         </activity>
 
-        <activity android:name=".security.SetupWizardScreenLockActivity"
-                  android:theme="@style/Theme.Car.Shared.SetupWizard.NoActionBar"
+        <activity android:name=".security.CheckLockActivity"
                   android:configChanges="orientation|keyboardHidden|screenSize"
                   android:windowSoftInputMode="adjustResize">
             <intent-filter>
-                <action android:name="android.car.settings.SETUP_WIZARD_SCREEN_LOCK_ACTIVITY" />
+                <action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL" />
+                <action android:name="android.app.action.CONFIRM_FRP_CREDENTIAL" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
 
+        <activity android:name=".system.ThirdPartyLicensesActivity"
+                  android:label="@string/settings_license_activity_title"
+                  android:configChanges="orientation|keyboardHidden|screenSize"
+                  android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.settings.THIRD_PARTY_LICENSE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".system.RegulatoryInfoDisplayActivity"
+            android:label="@string/regulatory_labels"
+            android:enabled="@bool/config_show_regulatory_info">
+            <intent-filter>
+                <action android:name="android.settings.SHOW_REGULATORY_INFO" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".location.LocationSettingsActivity"
+                  android:configChanges="orientation|keyboardHidden|screenSize"
+                  android:windowSoftInputMode="adjustResize"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.settings.LOCATION_SOURCE_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
+
+        <!-- This logic is copied from phone.-->
+        <!-- Ensures there's lightweight fallback activity when no other MAIN/HOME activity is present.-->
+        <activity android:name=".FallbackHome"
+                  android:excludeFromRecents="true"
+                  android:label=""
+                  android:screenOrientation="nosensor"
+                  android:theme="@style/FallbackHome">
+            <intent-filter android:priority="-900">
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.HOME" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
+
         <service android:name=".bluetooth.BluetoothPairingService" />
 
+        <service android:name=".setupservice.InitialLockSetupService"
+                 android:exported="true"
+                 android:permission="com.android.car.settings.SET_INITIAL_LOCK"/>
+
         <receiver android:name=".bluetooth.BluetoothPairingRequest">
             <intent-filter>
                 <action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
             </intent-filter>
         </receiver>
 
+        <!-- FileProvider to share a generated license html file.
+             Note that "com.android.settings.files" is set here as its authorities because a Uri
+             permission grant should be allowed to share a file with an external browser but it is
+             allowed only for Settings' authorities in ActivityManagerService.  -->
+        <provider android:name="androidx.core.content.FileProvider"
+                  android:authorities="com.android.settings.files"
+                  android:grantUriPermissions="true"
+                  android:exported="false">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                       android:resource="@xml/file_paths" />
+        </provider>
+
     </application>
 </manifest>
diff --git a/res/anim/fade_in_top.xml b/res/anim/fade_in_top.xml
deleted file mode 100644
index b573f21..0000000
--- a/res/anim/fade_in_top.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <alpha
-        android:fromAlpha="0.2"
-        android:toAlpha="1"
-        android:duration="@android:integer/config_mediumAnimTime" />
-    <translate
-        android:fromYDelta="-100%p"
-        android:toYDelta="0"
-        android:interpolator="@android:interpolator/linear_out_slow_in"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
diff --git a/res/anim/fade_out_top.xml b/res/anim/fade_out_top.xml
deleted file mode 100644
index e04518e..0000000
--- a/res/anim/fade_out_top.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="0.2"
-        android:duration="@android:integer/config_mediumAnimTime" />
-    <translate
-        android:fromYDelta="0"
-        android:toYDelta="-100%p"
-        android:interpolator="@android:interpolator/linear_out_slow_in"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
diff --git a/res/anim/trans_fade_in.xml b/res/anim/trans_fade_in.xml
index 86ea64c..518586a 100644
--- a/res/anim/trans_fade_in.xml
+++ b/res/anim/trans_fade_in.xml
@@ -1,24 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android">
-
     <alpha
+        android:duration="@android:integer/config_mediumAnimTime"
         android:fromAlpha="0.2"
-        android:toAlpha="1"
-        android:duration="@android:integer/config_mediumAnimTime" />
+        android:toAlpha="1"/>
 </set>
diff --git a/res/anim/trans_fade_out.xml b/res/anim/trans_fade_out.xml
index aaa22d5..d53aae0 100644
--- a/res/anim/trans_fade_out.xml
+++ b/res/anim/trans_fade_out.xml
@@ -1,24 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android">
-
     <alpha
+        android:duration="@android:integer/config_mediumAnimTime"
         android:fromAlpha="1.0"
-        android:toAlpha="0.2"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
+        android:toAlpha="0.2"/>
+</set>
diff --git a/res/anim/trans_right_in.xml b/res/anim/trans_right_in.xml
deleted file mode 100644
index 1a93266..0000000
--- a/res/anim/trans_right_in.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <translate
-        android:fromXDelta="100%p"
-        android:toXDelta="0"
-        android:interpolator="@android:interpolator/linear_out_slow_in"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
diff --git a/res/anim/trans_right_out.xml b/res/anim/trans_right_out.xml
deleted file mode 100644
index 9e1bc6e..0000000
--- a/res/anim/trans_right_out.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <translate
-        android:fromXDelta="0"
-        android:toXDelta="100%p"
-        android:interpolator="@android:interpolator/linear_out_slow_in"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
diff --git a/res/animator/trans_left_in.xml b/res/animator/trans_left_in.xml
index 2903660..ee3a124 100644
--- a/res/animator/trans_left_in.xml
+++ b/res/animator/trans_left_in.xml
@@ -1,32 +1,33 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
 <set xmlns:android="http://schemas.android.com/apk/res/android">
     <objectAnimator
+        android:duration="@android:integer/config_mediumAnimTime"
         android:interpolator="@android:interpolator/decelerate_quint"
+        android:propertyName="translationX"
         android:valueFrom="-100dp"
         android:valueTo="0dp"
-        android:valueType="floatType"
-        android:propertyName="translationX"
-        android:duration="@android:integer/config_mediumAnimTime" />
+        android:valueType="floatType"/>
     <objectAnimator
+        android:duration="@android:integer/config_mediumAnimTime"
         android:interpolator="@android:interpolator/decelerate_quint"
+        android:propertyName="alpha"
         android:valueFrom="0.0"
         android:valueTo="1.0"
-        android:valueType="floatType"
-        android:propertyName="alpha"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
+        android:valueType="floatType"/>
+</set>
diff --git a/res/animator/trans_left_out.xml b/res/animator/trans_left_out.xml
index eca6489..e5eaf2f 100644
--- a/res/animator/trans_left_out.xml
+++ b/res/animator/trans_left_out.xml
@@ -1,32 +1,33 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
 <set xmlns:android="http://schemas.android.com/apk/res/android">
     <objectAnimator
+        android:duration="@android:integer/config_mediumAnimTime"
         android:interpolator="@android:interpolator/decelerate_quint"
+        android:propertyName="translationX"
         android:valueFrom="0dp"
         android:valueTo="-100dp"
-        android:valueType="floatType"
-        android:propertyName="translationX"
-        android:duration="@android:integer/config_mediumAnimTime" />
+        android:valueType="floatType"/>
     <objectAnimator
+        android:duration="@android:integer/config_mediumAnimTime"
         android:interpolator="@android:interpolator/decelerate_quint"
+        android:propertyName="alpha"
         android:valueFrom="1.0"
         android:valueTo="0.0"
-        android:valueType="floatType"
-        android:propertyName="alpha"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
+        android:valueType="floatType"/>
+</set>
diff --git a/res/animator/trans_right_in.xml b/res/animator/trans_right_in.xml
index bd4e588..c9c339b 100644
--- a/res/animator/trans_right_in.xml
+++ b/res/animator/trans_right_in.xml
@@ -1,34 +1,33 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 2017, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
 -->
+
 <set xmlns:android="http://schemas.android.com/apk/res/android">
     <objectAnimator
+        android:duration="@android:integer/config_mediumAnimTime"
         android:interpolator="@android:interpolator/decelerate_quint"
+        android:propertyName="translationX"
         android:valueFrom="100dp"
         android:valueTo="0dp"
-        android:valueType="floatType"
-        android:propertyName="translationX"
-        android:duration="@android:integer/config_mediumAnimTime" />
+        android:valueType="floatType"/>
     <objectAnimator
+        android:duration="@android:integer/config_mediumAnimTime"
         android:interpolator="@android:interpolator/decelerate_quint"
+        android:propertyName="alpha"
         android:valueFrom="0.0"
         android:valueTo="1.0"
-        android:valueType="floatType"
-        android:propertyName="alpha"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
+        android:valueType="floatType"/>
+</set>
diff --git a/res/animator/trans_right_out.xml b/res/animator/trans_right_out.xml
index 803e1e4..df7e75b 100644
--- a/res/animator/trans_right_out.xml
+++ b/res/animator/trans_right_out.xml
@@ -1,34 +1,33 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 2017, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
 -->
+
 <set xmlns:android="http://schemas.android.com/apk/res/android">
     <objectAnimator
+        android:duration="@android:integer/config_mediumAnimTime"
         android:interpolator="@android:interpolator/decelerate_quint"
+        android:propertyName="translationX"
         android:valueFrom="0dp"
         android:valueTo="100dp"
-        android:valueType="floatType"
-        android:propertyName="translationX"
-        android:duration="@android:integer/config_mediumAnimTime" />
+        android:valueType="floatType"/>
     <objectAnimator
+        android:duration="@android:integer/config_mediumAnimTime"
         android:interpolator="@android:interpolator/decelerate_quint"
+        android:propertyName="alpha"
         android:valueFrom="1.0"
         android:valueTo="0.0"
-        android:valueType="floatType"
-        android:propertyName="alpha"
-        android:duration="@android:integer/config_mediumAnimTime" />
-</set>
\ No newline at end of file
+        android:valueType="floatType"/>
+</set>
diff --git a/res/color/action_bar_btn.xml b/res/color/action_bar_btn.xml
deleted file mode 100644
index a6446d8..0000000
--- a/res/color/action_bar_btn.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_accent" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_accent" />
-</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_regular.xml b/res/color/lock_pattern_regular.xml
deleted file mode 100644
index 67e4fba..0000000
--- a/res/color/lock_pattern_regular.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_body1" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_body1" />
-</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_regular_dark.xml b/res/color/lock_pattern_regular_dark.xml
deleted file mode 100644
index d3ba4ed..0000000
--- a/res/color/lock_pattern_regular_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_body1_dark" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_body1_dark" />
-</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_regular_light.xml b/res/color/lock_pattern_regular_light.xml
deleted file mode 100644
index 5b47d6b..0000000
--- a/res/color/lock_pattern_regular_light.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_body1_light" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_body1_light" />
-</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_success.xml b/res/color/lock_pattern_success.xml
deleted file mode 100644
index bee9145..0000000
--- a/res/color/lock_pattern_success.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_blue_500" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_blue_500" />
-</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_success_dark.xml b/res/color/lock_pattern_success_dark.xml
deleted file mode 100644
index 33ad70e..0000000
--- a/res/color/lock_pattern_success_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_teal_700" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_teal_700" />
-</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_success_light.xml b/res/color/lock_pattern_success_light.xml
deleted file mode 100644
index eb834c4..0000000
--- a/res/color/lock_pattern_success_light.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_teal_200" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_teal_200" />
-</selector>
\ No newline at end of file
diff --git a/res/color/text_body_1.xml b/res/color/text_body_1.xml
deleted file mode 100644
index e7b5f40..0000000
--- a/res/color/text_body_1.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_body1" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_body1" />
-</selector>
\ No newline at end of file
diff --git a/res/color/text_body_2.xml b/res/color/text_body_2.xml
deleted file mode 100644
index 543d2ce..0000000
--- a/res/color/text_body_2.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="true"
-          android:color="@color/car_body2" />
-    <item android:state_enabled="false"
-          android:alpha="0.5"
-          android:color="@color/car_body2" />
-</selector>
\ No newline at end of file
diff --git a/res/color/toggle_bg.xml b/res/color/toggle_bg.xml
index f48f216..8b5e8d6 100644
--- a/res/color/toggle_bg.xml
+++ b/res/color/toggle_bg.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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
+         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,
@@ -16,9 +16,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-  <item android:state_enabled="false"
-      android:color="@color/toggle_bg_disabled"/>
-  <item android:state_enabled="true"
-      android:color="@color/car_accent"/>
-  <item android:color="@color/car_accent"/>
-</selector>
\ No newline at end of file
+    <item
+        android:color="?attr/quickSettingsEnabledColor"
+        android:state_enabled="true"/>
+    <item android:color="?attr/quickSettingsDisabledColor"/>
+</selector>
diff --git a/res/color/toggle_icon_tint.xml b/res/color/toggle_icon_tint.xml
index 41388ba..a977107 100644
--- a/res/color/toggle_icon_tint.xml
+++ b/res/color/toggle_icon_tint.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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
+         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,
@@ -16,9 +16,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-  <item android:state_enabled="false"
-      android:color="@color/toggle_icon_disabled"/>
-  <item android:state_enabled="true"
-      android:color="@color/car_card"/>
-  <item android:color="@color/car_card"/>
-</selector>
\ No newline at end of file
+    <item
+        android:color="?attr/quickSettingsIconEnabledColor"
+        android:state_enabled="true"/>
+    <item android:color="?attr/quickSettingsIconDisabledColor"/>
+</selector>
diff --git a/res/drawable/brightness_seekbar_track.xml b/res/drawable/brightness_seekbar_track.xml
new file mode 100644
index 0000000..7cacf25
--- /dev/null
+++ b/res/drawable/brightness_seekbar_track.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Use android provided id, as seekbar is expecting that -->
+    <item android:id="@android:id/background">
+        <shape android:shape="line">
+            <corners android:radius="@dimen/brightness_seekbar_track_corner"/>
+            <stroke android:width="@dimen/brightness_seekbar_track_height"/>
+        </shape>
+    </item>
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape android:shape="line">
+                <stroke android:width="@dimen/brightness_seekbar_track_height"/>
+            </shape>
+        </clip>
+    </item>
+</layer-list>
diff --git a/res/drawable/button_ripple_bg.xml b/res/drawable/button_ripple_bg.xml
index 5727754..a32ea39 100644
--- a/res/drawable/button_ripple_bg.xml
+++ b/res/drawable/button_ripple_bg.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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
+         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,
@@ -15,9 +15,10 @@
     limitations under the License.
 -->
 
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/car_card_ripple_background"
-        android:radius="@dimen/toggle_ripple">
-    <item android:id="@android:id/mask"
-          android:drawable="@drawable/rectangle_ripple_mask" />
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item
+        android:id="@android:id/mask"
+        android:drawable="@drawable/rectangle_ripple_mask"/>
 </ripple>
diff --git a/res/drawable/car_add_circle_round.xml b/res/drawable/car_add_circle_round.xml
deleted file mode 100644
index 74fd8a4..0000000
--- a/res/drawable/car_add_circle_round.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 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.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item>
-        <shape android:shape="oval">
-            <solid
-                android:color="@color/car_user_switcher_add_user_background_color"/>
-            <size
-                android:width="@dimen/car_user_switcher_image_avatar_size"
-                android:height="@dimen/car_user_switcher_image_avatar_size"/>
-        </shape>
-    </item>
-    <item
-        android:drawable="@drawable/car_ic_add"
-        android:gravity="center"/>
-</layer-list>
\ No newline at end of file
diff --git a/res/drawable/car_ic_add.xml b/res/drawable/car_ic_add.xml
deleted file mode 100644
index 9216efc..0000000
--- a/res/drawable/car_ic_add.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-    Copyright (C) 2018 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="@dimen/car_touch_target_size"
-    android:height="@dimen/car_touch_target_size"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-  <path
-      android:fillColor="@color/car_user_switcher_add_user_add_sign_color"
-      android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
-</vector>
\ No newline at end of file
diff --git a/res/drawable/car_user_avatar_bg_circle.xml b/res/drawable/car_user_avatar_bg_circle.xml
deleted file mode 100644
index 55840f0..0000000
--- a/res/drawable/car_user_avatar_bg_circle.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 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"
-    android:shape="oval">
-
-  <stroke
-      android:color="@color/car_user_switcher_current_user_color"
-      android:width="@dimen/car_user_switcher_current_user_circle_stroke_width"/>
-
-  <size
-      android:width="@dimen/car_user_switcher_current_user_circle_width"
-      android:height="@dimen/car_user_switcher_current_user_circle_width"/>
-
-</shape>
\ No newline at end of file
diff --git a/res/drawable/circle_bg.xml b/res/drawable/circle_bg.xml
index 5fc37ad..7b56ee0 100644
--- a/res/drawable/circle_bg.xml
+++ b/res/drawable/circle_bg.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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
+         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,
@@ -18,11 +18,9 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="oval">
-
-  <solid
-      android:color="@color/toggle_bg"/>
-
-  <size
-      android:width="@dimen/car_touch_target_size"
-      android:height="@dimen/car_touch_target_size"/>
-</shape>
\ No newline at end of file
+    <solid
+        android:color="@color/toggle_bg"/>
+    <size
+        android:width="@dimen/touch_target_size"
+        android:height="@dimen/touch_target_size"/>
+</shape>
diff --git a/res/drawable/circle_ripple_bg.xml b/res/drawable/circle_ripple_bg.xml
index c9c4cf8..85a9adc 100644
--- a/res/drawable/circle_ripple_bg.xml
+++ b/res/drawable/circle_ripple_bg.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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
+         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,
@@ -15,7 +15,8 @@
     limitations under the License.
 -->
 
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/car_card_ripple_background"
-        android:radius="@dimen/toggle_ripple">
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight"
+    android:radius="@dimen/circle_ripple_bg_radius">
 </ripple>
diff --git a/res/drawable/color_progress_bar.xml b/res/drawable/color_progress_bar.xml
new file mode 100644
index 0000000..10a2891
--- /dev/null
+++ b/res/drawable/color_progress_bar.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background"
+          android:gravity="center_vertical|fill_horizontal">
+        <shape android:shape="rectangle"
+               android:tint="@color/config_progress_background_tint">
+            <solid android:color="@color/white_disabled_material"/>
+        </shape>
+    </item>
+    <item android:id="@android:id/progress"
+          android:gravity="center_vertical|fill_horizontal">
+        <scale android:scaleWidth="100%">
+            <shape android:shape="rectangle"
+                   android:tint="?android:attr/colorControlActivated">
+                <solid android:color="@android:color/white"/>
+            </shape>
+        </scale>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/divider.xml b/res/drawable/divider.xml
new file mode 100644
index 0000000..e6ed481
--- /dev/null
+++ b/res/drawable/divider.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<inset
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:insetLeft="@dimen/divider_inset_left"
+    android:insetRight="@dimen/divider_inset_right">
+    <shape>
+        <size android:height="@dimen/divider_height"/>
+        <solid android:color="?attr/dividerColor"/>
+    </shape>
+</inset>
diff --git a/res/drawable/ic_account.xml b/res/drawable/ic_account.xml
index 8dba222..f7a666d 100644
--- a/res/drawable/ic_account.xml
+++ b/res/drawable/ic_account.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M3,5v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2L5,3c-1.11,0 -2,0.9 -2,2zM15,9c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3zM6,17c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1L6,18v-1z"/>
 </vector>
diff --git a/res/drawable/ic_add.xml b/res/drawable/ic_add.xml
index 1282ade..458f8bf 100644
--- a/res/drawable/ic_add.xml
+++ b/res/drawable/ic_add.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint" >
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?attr/iconColor"
         android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
 </vector>
diff --git a/res/drawable/ic_arrow_back.xml b/res/drawable/ic_arrow_back.xml
index 1314fef..f23c621 100644
--- a/res/drawable/ic_arrow_back.xml
+++ b/res/drawable/ic_arrow_back.xml
@@ -1,30 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 
 <!-- This Icon is used in as the back icon on ActionBar. ActionBar hard code the icon layout and
   ~  does not provide a way to customize it. Here to center the icon in action bar, we make up
   ~  the margin by add the extra space in the icon itself -->
 <vector
-    android:height="@dimen/car_primary_icon_size"
-    android:width="@dimen/car_primary_icon_size"
-    android:tint="@color/car_accent"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
     android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    xmlns:android="http://schemas.android.com/apk/res/android">
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?attr/iconColor"
         android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
 </vector>
diff --git a/res/drawable/ic_arrow_drop_down.xml b/res/drawable/ic_arrow_drop_down.xml
index 487b2ff..854660a 100644
--- a/res/drawable/ic_arrow_drop_down.xml
+++ b/res/drawable/ic_arrow_drop_down.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:height="@dimen/car_primary_icon_size"
-        android:width="@dimen/car_primary_icon_size"
-        android:tint="@color/car_tint"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:pathData="M7,10l5,5 5,-5z"
-        android:fillColor="#000000"/>
+        android:fillColor="?attr/iconColor"
+        android:pathData="M7,10l5,5 5,-5z"/>
 </vector>
diff --git a/res/drawable/ic_arrow_forward.xml b/res/drawable/ic_arrow_forward.xml
index 42fcbca..2b6b63c 100644
--- a/res/drawable/ic_arrow_forward.xml
+++ b/res/drawable/ic_arrow_forward.xml
@@ -1,27 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path android:pathData="M0 0h24v24H0z"/>
     <path
-        android:pathData="M0 0h24v24H0z" />
-    <path
-        android:fillColor="#FFFFFF"
-        android:pathData="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
+        android:fillColor="?attr/iconColor"
+        android:pathData="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"/>
 </vector>
diff --git a/res/drawable/ic_arrow_forward_on_disc.xml b/res/drawable/ic_arrow_forward_on_disc.xml
deleted file mode 100644
index 6cff5e5..0000000
--- a/res/drawable/ic_arrow_forward_on_disc.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<layer-list xmlns:android = "http://schemas.android.com/apk/res/android">
-    <item>
-        <shape android:shape="oval">
-            <solid android:color="@color/google_blue_600" />
-            <size
-                android:width="@dimen/pin_pad_icon_size"
-                android:height="@dimen/pin_pad_icon_size" />
-        </shape>
-    </item>
-    <item android:drawable="@drawable/ic_arrow_forward" />
-</layer-list>
-
diff --git a/res/drawable/ic_audio_navi.xml b/res/drawable/ic_audio_navi.xml
index f2b1517..afc8a61 100644
--- a/res/drawable/ic_audio_navi.xml
+++ b/res/drawable/ic_audio_navi.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint" >
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?attr/iconColor"
         android:pathData="M18.92,5.01C18.72,4.42 18.16,4 17.5,4h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,11v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,12 6.5,12s1.5,0.67 1.5,1.5S7.33,15 6.5,15zM17.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,10l1.5,-4.5h11L19,10L5,10z"/>
 </vector>
diff --git a/res/drawable/ic_backspace.xml b/res/drawable/ic_backspace.xml
index fb0bb24..d2ea585 100644
--- a/res/drawable/ic_backspace.xml
+++ b/res/drawable/ic_backspace.xml
@@ -1,25 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?attr/iconColor"
         android:pathData="M22,3H7C6.31,3 5.77,3.35 5.41,3.88l-5.04,7.57c-0.22,0.34 -0.22,0.77 0,1.11l5.04,7.56C5.77,20.64 6.31,21 7,21h15c1.1,0 2,-0.9 2,-2V5C24,3.9 23.1,3 22,3zM18.3,16.3L18.3,16.3c-0.39,0.39 -1.02,0.39 -1.41,0L14,13.41l-2.89,2.89c-0.39,0.39 -1.02,0.39 -1.41,0h0c-0.39,-0.39 -0.39,-1.02 0,-1.41L12.59,12L9.7,9.11c-0.39,-0.39 -0.39,-1.02 0,-1.41l0,0c0.39,-0.39 1.02,-0.39 1.41,0L14,10.59l2.89,-2.89c0.39,-0.39 1.02,-0.39 1.41,0v0c0.39,0.39 0.39,1.02 0,1.41L15.41,12l2.89,2.89C18.68,15.27 18.68,15.91 18.3,16.3z"/>
 </vector>
diff --git a/res/drawable/ic_brightness_knob.xml b/res/drawable/ic_brightness_knob.xml
index 603a6f4..db2331a 100644
--- a/res/drawable/ic_brightness_knob.xml
+++ b/res/drawable/ic_brightness_knob.xml
@@ -1,26 +1,27 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"
-        android:fillColor="@color/car_accent"/>
+        android:fillColor="?attr/quickSettingsEnabledColor"
+        android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"/>
 </vector>
diff --git a/res/drawable/ic_brightness_knob_stretched.xml b/res/drawable/ic_brightness_knob_stretched.xml
index f93a744..3e1b53a 100644
--- a/res/drawable/ic_brightness_knob_stretched.xml
+++ b/res/drawable/ic_brightness_knob_stretched.xml
@@ -1,31 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item>
         <shape>
             <size
-                android:height="@dimen/brightness_knob_size"
-                android:width="@dimen/brightness_knob_size" />
-
-            <solid android:color="@android:color/transparent" />
+                android:width="@dimen/brightness_knob_size"
+                android:height="@dimen/brightness_knob_size"/>
+            <solid android:color="@android:color/transparent"/>
         </shape>
     </item>
     <item android:drawable="@drawable/ic_brightness_knob"/>
-
 </layer-list>
diff --git a/res/drawable/ic_bt_imaging.xml b/res/drawable/ic_bt_imaging.xml
deleted file mode 100644
index c65a7c2..0000000
--- a/res/drawable/ic_bt_imaging.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-     Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0"
-    android:tint="@color/car_tint">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M19,8L5,8c-1.66,0 -3,1.34 -3,3v6h4v4h12v-4h4v-6c0,-1.66 -1.34,-3
-            -3,-3zM16,19L8,19v-5h8v5zM19,12c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1
-            -1,1zM18,3L6,3v4h12L18,3z"/>
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_bt_laptop.xml b/res/drawable/ic_bt_laptop.xml
deleted file mode 100644
index a9c70ff..0000000
--- a/res/drawable/ic_bt_laptop.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-     Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0"
-    android:tint="@color/car_tint">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M20,18c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0
-            -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"/>
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_cellular_data.xml b/res/drawable/ic_cellular_data.xml
index 5a0437c..9b0b27f 100644
--- a/res/drawable/ic_cellular_data.xml
+++ b/res/drawable/ic_cellular_data.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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.
@@ -13,14 +14,16 @@
     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="24.0dp"
-    android:height="24.0dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
+        android:fillColor="?attr/iconColor"
         android:pathData="M12.0,12.0m-9.5,0.0a9.5,9.5 0.0,1.0 1.0,19.0 0.0a9.5,9.5 0.0,1.0 1.0,-19.0 0.0
         M10.6,5.4c-0.2,-0.2 -0.5,-0.2 -0.6,0.0L7.6,9.1l2.0,0.0l0.0,3.8L11.0,12.900001L11.0,9.1l2.0,0.0L10.6,5.4z
-        M13.3,18.6c0.2,0.2 0.5,0.2 0.6,0.0l2.4,-3.7l-2.0,0.0l0.0,-3.8l-1.4,0.0l0.0,3.8l-2.0,0.0L13.3,18.6z"
-        android:fillColor="#ffffff"/>
+        M13.3,18.6c0.2,0.2 0.5,0.2 0.6,0.0l2.4,-3.7l-2.0,0.0l0.0,-3.8l-1.4,0.0l0.0,3.8l-2.0,0.0L13.3,18.6z"/>
 </vector>
diff --git a/res/drawable/ic_check.xml b/res/drawable/ic_check.xml
index fce0802..9838470 100644
--- a/res/drawable/ic_check.xml
+++ b/res/drawable/ic_check.xml
@@ -1,27 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
     <path
-        android:pathData="M0 0h24v24H0z" />
+        android:pathData="M0 0h24v24H0z"/>
     <path
-        android:fillColor="#FFFFFF"
-        android:pathData="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
+        android:fillColor="?attr/iconColor"
+        android:pathData="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
 </vector>
diff --git a/res/drawable/ic_check_box.xml b/res/drawable/ic_check_box.xml
new file mode 100644
index 0000000..5a7515c
--- /dev/null
+++ b/res/drawable/ic_check_box.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:drawable="@drawable/ic_check_box_unchecked"
+        android:state_checked="false"/>
+    <item
+        android:drawable="@drawable/ic_check_box_checked"
+        android:state_checked="true"/>
+    <item android:drawable="@drawable/ic_check_box_unchecked"/>
+</selector>
diff --git a/res/drawable/ic_check_box_checked.xml b/res/drawable/ic_check_box_checked.xml
index 25a55d4..479163d 100644
--- a/res/drawable/ic_check_box_checked.xml
+++ b/res/drawable/ic_check_box_checked.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@dimen/car_primary_icon_size"
-        android:height="@dimen/car_primary_icon_size"
-        android:tint="@color/car_accent"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"
-        android:fillColor="#000000"/>
+        android:fillColor="?attr/iconColor"
+        android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
 </vector>
diff --git a/res/drawable/ic_check_box_unchecked.xml b/res/drawable/ic_check_box_unchecked.xml
index a88452f..70dbcd1 100644
--- a/res/drawable/ic_check_box_unchecked.xml
+++ b/res/drawable/ic_check_box_unchecked.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@dimen/car_primary_icon_size"
-        android:height="@dimen/car_primary_icon_size"
-        android:tint="@color/car_tint"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"
-        android:fillColor="#000000"/>
+        android:fillColor="?attr/iconColor"
+        android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
 </vector>
diff --git a/res/drawable/ic_check_on_disc.xml b/res/drawable/ic_check_on_disc.xml
deleted file mode 100644
index 687e199..0000000
--- a/res/drawable/ic_check_on_disc.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<layer-list xmlns:android = "http://schemas.android.com/apk/res/android">
-    <item>
-        <shape android:shape="oval">
-            <solid android:color="@color/google_blue_600" />
-            <size
-                android:width="@dimen/pin_pad_icon_size"
-                android:height="@dimen/pin_pad_icon_size" />
-        </shape>
-    </item>
-    <item android:drawable="@drawable/ic_check" />
-
-</layer-list>
-
diff --git a/res/drawable/ic_chevron_right.xml b/res/drawable/ic_chevron_right.xml
index a2e3745..ea9a763 100644
--- a/res/drawable/ic_chevron_right.xml
+++ b/res/drawable/ic_chevron_right.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?attr/iconColor"
         android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
 </vector>
diff --git a/res/drawable/ic_close.xml b/res/drawable/ic_close.xml
index 377a889..07c42ef 100644
--- a/res/drawable/ic_close.xml
+++ b/res/drawable/ic_close.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:height="@dimen/car_primary_icon_size"
-        android:width="@dimen/car_primary_icon_size"
-        android:tint="@color/car_tint"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
-        android:fillColor="#000000"/>
+        android:fillColor="?attr/iconColor"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
 </vector>
diff --git a/res/drawable/ic_delete.xml b/res/drawable/ic_delete.xml
deleted file mode 100644
index 84552c1..0000000
--- a/res/drawable/ic_delete.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-    <path
-        android:fillColor="@color/car_tint"
-        android:pathData="M12 38c0 2.21 1.79 4 4 4h16c2.21 0 4,-1.79 4,-4V14H12v24zM38 8h-7l-2,-2H19l-2 2h-7v4h28V8z"/>
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_done.xml b/res/drawable/ic_done.xml
deleted file mode 100644
index 3637853..0000000
--- a/res/drawable/ic_done.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ Copyright (C) 2018 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.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M9,16.2l-3.5,-3.5a0.984,0.984 0,0 0,-1.4 0,0.984 0.984,0 0,0 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7a0.984,0.984 0,0 0,0 -1.4,0.984 0.984,0 0,0 -1.4,0L9,16.2z"/>
-</vector>
diff --git a/res/drawable/ic_edit.xml b/res/drawable/ic_edit.xml
new file mode 100644
index 0000000..8e3332e
--- /dev/null
+++ b/res/drawable/ic_edit.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M20.41,4.94l-1.35,-1.35c-0.78,-0.78 -2.05,-0.78 -2.83,0l0,0L3,16.82V21h4.18L20.41,7.77C21.2,6.99 21.2,5.72 20.41,4.94zM6.41,19.06L5,19v-1.36l9.82,-9.82l1.41,1.41L6.41,19.06z"/>
+</vector>
diff --git a/res/drawable/ic_folder.xml b/res/drawable/ic_folder.xml
new file mode 100644
index 0000000..aed22cf
--- /dev/null
+++ b/res/drawable/ic_folder.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/icon_size"
+        android:height="@dimen/icon_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M20,6h-8l-2,-2H4C2.9,4 2.01,4.9 2.01,6L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8C22,6.9 21.1,6 20,6zM20,18H4V8h16V18z"/>
+</vector>
diff --git a/res/drawable/ic_guest_user.xml b/res/drawable/ic_guest_user.xml
deleted file mode 100644
index 1e684b3..0000000
--- a/res/drawable/ic_guest_user.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
-</vector>
diff --git a/res/drawable/ic_headset.xml b/res/drawable/ic_headset.xml
new file mode 100644
index 0000000..0de143e
--- /dev/null
+++ b/res/drawable/ic_headset.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/icon_size"
+        android:height="@dimen/icon_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M19,15v3c0,0.55 -0.45,1 -1,1h-1v-4H19M7,15v4H6c-0.55,0 -1,-0.45 -1,-1v-3H7M12,2c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7C21,6.03 16.97,2 12,2L12,2z"/>
+</vector>
diff --git a/res/drawable/ic_keyboard_arrow_down.xml b/res/drawable/ic_keyboard_arrow_down.xml
new file mode 100644
index 0000000..45eda10
--- /dev/null
+++ b/res/drawable/ic_keyboard_arrow_down.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
+</vector>
diff --git a/res/drawable/ic_keyboard_arrow_up.xml b/res/drawable/ic_keyboard_arrow_up.xml
new file mode 100644
index 0000000..4437437
--- /dev/null
+++ b/res/drawable/ic_keyboard_arrow_up.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
+</vector>
diff --git a/res/drawable/ic_language.xml b/res/drawable/ic_language.xml
index 43cdb7e..eddea9a 100644
--- a/res/drawable/ic_language.xml
+++ b/res/drawable/ic_language.xml
@@ -1,25 +1,27 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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="@dimen/car_primary_icon_size"
-        android:height="@dimen/car_primary_icon_size"
-        android:viewportHeight="48.0"
-        android:viewportWidth="48.0">
+    Copyright 2018 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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
     <path
-        android:fillColor="@color/car_tint"
+        android:fillColor="?attr/iconColor"
         android:pathData="M23.99,4C12.94,4 4,12.95 4,24s8.94,20 19.99,20C35.04,44 44,35.05 44,24S35.04,4 23.99,4zM37.84,16h-5.9c-0.65,-2.5 -1.56,-4.9 -2.76,-7.12 3.68,1.26 6.74,3.81 8.66,7.12zM24,8.07c1.67,2.4 2.97,5.07 3.82,7.93h-7.64c0.85,-2.86 2.15,-5.53 3.82,-7.93zM8.52,28C8.19,26.72 8,25.38 8,24s0.19,-2.72 0.52,-4h6.75c-0.16,1.31 -0.27,2.64 -0.27,4 0,1.36 0.11,2.69 0.28,4L8.52,28zM10.15,32h5.9c0.65,2.5 1.56,4.9 2.76,7.13 -3.68,-1.26 -6.74,-3.82 -8.66,-7.13zM16.05,16h-5.9c1.92,-3.31 4.98,-5.87 8.66,-7.13 -1.2,2.23 -2.11,4.63 -2.76,7.13zM24,39.93c-1.66,-2.4 -2.96,-5.07 -3.82,-7.93h7.64c-0.86,2.86 -2.16,5.53 -3.82,7.93zM28.68,28h-9.36c-0.19,-1.31 -0.32,-2.64 -0.32,-4 0,-1.36 0.13,-2.69 0.32,-4h9.36c0.19,1.31 0.32,2.64 0.32,4 0,1.36 -0.13,2.69 -0.32,4zM29.19,39.12c1.2,-2.23 2.11,-4.62 2.76,-7.12h5.9c-1.93,3.31 -4.99,5.86 -8.66,7.12zM32.72,28c0.16,-1.31 0.28,-2.64 0.28,-4 0,-1.36 -0.11,-2.69 -0.28,-4h6.75c0.33,1.28 0.53,2.62 0.53,4s-0.19,2.72 -0.53,4h-6.75z"/>
 </vector>
diff --git a/res/drawable/ic_lock.xml b/res/drawable/ic_lock.xml
index 5cdfe79..4caeaf1 100644
--- a/res/drawable/ic_lock.xml
+++ b/res/drawable/ic_lock.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 Google Inc.
+    Copyright 2018 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
+         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,
@@ -14,6 +14,14 @@
     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="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
-    <path android:pathData="M36,16h-2v-4c0,-5.52 -4.48,-10 -10,-10S14,6.48 14,12v4h-2c-2.21,0 -4,1.79 -4,4v20c0,2.21 1.79,4 4,4h24c2.21,0 4,-1.79 4,-4L40,20c0,-2.21 -1.79,-4 -4,-4zM24,34c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4zM30.2,16L17.8,16v-4c0,-3.42 2.78,-6.2 6.2,-6.2 3.42,0 6.2,2.78 6.2,6.2v4z" android:fillColor="@color/google_blue_600"/>
-</vector>
\ No newline at end of file
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M36,16h-2v-4c0,-5.52 -4.48,-10 -10,-10S14,6.48 14,12v4h-2c-2.21,0 -4,1.79 -4,4v20c0,2.21 1.79,4 4,4h24c2.21,0 4,-1.79 4,-4L40,20c0,-2.21 -1.79,-4 -4,-4zM24,34c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4zM30.2,16L17.8,16v-4c0,-3.42 2.78,-6.2 6.2,-6.2 3.42,0 6.2,2.78 6.2,6.2v4z"/>
+</vector>
diff --git a/res/drawable/ic_media_stream.xml b/res/drawable/ic_media_stream.xml
new file mode 100644
index 0000000..c07d1e0
--- /dev/null
+++ b/res/drawable/ic_media_stream.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/icon_size"
+        android:height="@dimen/icon_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M12,3l0.01,10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17V7h4V3H12zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/>
+</vector>
diff --git a/res/drawable/ic_remove_circle.xml b/res/drawable/ic_remove_circle.xml
new file mode 100644
index 0000000..16ce5d3
--- /dev/null
+++ b/res/drawable/ic_remove_circle.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="48"
+    android:viewportWidth="48">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M24,4C12.95,4,4,12.95,4,24
+                         s8.95,20,20,20,20-8.95,20-20
+                         S35.05,4,24,4zm10,22H14v-4h20v4z"/>
+</vector>
diff --git a/res/drawable/ic_restore.xml b/res/drawable/ic_restore.xml
new file mode 100644
index 0000000..0c457f5
--- /dev/null
+++ b/res/drawable/ic_restore.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M13.5 7.75v4.5l3.37 2c.34 .2 .46 .65 .25 .99 s-.64 .45 -.98 .24 L12
+13V7.75c0-.41 .34 -.75 .75 -.75s.75 .34 .75 .75 zM13.05 3C8.11 3 4.1 7.02 4.1
+11.95c0 .02 .01 .03 .01 .05H2.05c-.47 0-.71 .57 -.37 .9 l2.95 2.94c.21 .21 .54
+.21 .75 0l2.95-2.94c.33-.33 .1 -.9-.37-.9H5.99c0-.02 .01 -.03 .01 -.05C6 8.06
+9.16 4.9 13.05 4.9S20.1 8.11 20.1 12s-3.16 7.1-7.05 7.1c-1.58
+0-3.08-.51-4.32-1.48a.94 .94 0 0 0-1.32 .16 l-.01 .01 a.94 .94 0 0 0 .16
+1.32l.01 .01 A8.77 8.77 0 0 0 13.05 21c4.94 0 8.95-4.07 8.95-9s-4.02-9-8.95-9z"/>
+</vector>
diff --git a/res/drawable/ic_seekbar_thumb.xml b/res/drawable/ic_seekbar_thumb.xml
deleted file mode 100644
index 2810356..0000000
--- a/res/drawable/ic_seekbar_thumb.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-    <solid android:color="@color/car_accent" />
-    <size
-        android:width="30dp"
-        android:height="30dp" />
-    <stroke
-        android:width="1dp"
-        android:color="@color/car_accent" />
-</shape>
diff --git a/res/drawable/ic_seekbar_track.xml b/res/drawable/ic_seekbar_track.xml
deleted file mode 100644
index 8c5acff..0000000
--- a/res/drawable/ic_seekbar_track.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- Use android provided id, as seekbar is expecting that -->
-    <item android:id="@android:id/background">
-        <shape android:shape="line">
-            <corners
-                android:radius="@dimen/seekbar_track_corner"/>
-            <stroke
-                android:width="@dimen/seekbar_track_height"
-                android:color="@color/seekbar_track" />
-        </shape>
-    </item>
-    <item android:id="@android:id/progress">
-        <clip>
-            <shape android:shape="line">
-                <stroke
-                    android:width="@dimen/seekbar_track_height"
-                    android:color="@color/car_accent" />
-            </shape>
-        </clip>
-    </item>
-</layer-list>
\ No newline at end of file
diff --git a/res/drawable/ic_settings_about.xml b/res/drawable/ic_settings_about.xml
index 57631a5..75647d0 100644
--- a/res/drawable/ic_settings_about.xml
+++ b/res/drawable/ic_settings_about.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,13 +14,14 @@
     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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M11.0,17.0l2.0,0.0l0.0,-6.0l-2.0,0.0l0.0,6.0zm1.0,-15.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0zm0.0,18.0c-4.41,0.0 -8.0,-3.59 -8.0,-8.0s3.59,-8.0 8.0,-8.0 8.0,3.59 8.0,8.0 -3.59,8.0 -8.0,8.0zM11.0,9.0l2.0,0.0L13.0,7.0l-2.0,0.0l0.0,2.0z"/>
 </vector>
diff --git a/res/drawable/ic_settings_applications.xml b/res/drawable/ic_settings_applications.xml
index f9c306b..b970c3d 100644
--- a/res/drawable/ic_settings_applications.xml
+++ b/res/drawable/ic_settings_applications.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,13 +14,14 @@
     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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M5.0,16.0c0.0,3.87 3.13,7.0 7.0,7.0s7.0,-3.13 7.0,-7.0l0.0,-4.0L5.0,12.0l0.0,4.0zM16.12,4.37l2.1,-2.1 -0.82,-0.83 -2.3,2.31C14.16,3.28 13.12,3.0 12.0,3.0s-2.1,0.28 -3.0,0.75L6.6,1.44l-0.8,0.83 2.1,2.1C6.14,5.64 5.0,7.68 5.0,10.0l0.0,1.0l14.0,0.0l0.0,-1.0c0.0,-2.32 -1.14,-4.36 -2.88,-5.63zM9.0,9.0c-0.55,0.0 -1.0,-0.45 -1.0,-1.0s0.45,-1.0 1.0,-1.0 1.0,0.45 1.0,1.0 -0.45,1.0 -1.0,1.0zm6.0,0.0c-0.55,0.0 -1.0,-0.45 -1.0,-1.0s0.45,-1.0 1.0,-1.0 1.0,0.45 1.0,1.0 -0.45,1.0 -1.0,1.0z"/>
 </vector>
diff --git a/res/drawable/ic_settings_auto_wifi.xml b/res/drawable/ic_settings_auto_wifi.xml
new file mode 100644
index 0000000..5eddaf1
--- /dev/null
+++ b/res/drawable/ic_settings_auto_wifi.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/icon_size"
+        android:height="@dimen/icon_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M18 10l3 0 -10 -9 -10 9 3 0 0 8 7.1 0c0 -0.3 -0.1 -0.7 -0.1 -1 0 -3.9 3.1 -7 7 -7z"
+        android:fillColor="?attr/iconColor"/>
+    <path
+        android:pathData="M18 14l0 1.6 2.1 -2.1 -2.1 -2.2 0 1.6c-2.3 0 -4.2 1.9 -4.2 4.3 0 0.8 0.2 1.6 0.7 2.3l0.8 -0.8C15.1 18.3 14.9 17.8 14.9 17.2 14.8 15.4 16.2 14 18 14"
+        android:strokeWidth="0.5"
+        android:fillColor="?attr/iconColor"
+        android:strokeMiterLimit="10" />
+    <path
+        android:pathData="M20.8 15.7c0.2 0.4 0.4 0.9 0.4 1.5 0 1.8 -1.4 3.2 -3.2 3.2l0 -1.6 -2.1 2.1 2.1 2.1 0 -1.6c2.3 0 4.2 -1.9 4.2 -4.2 0 -0.8 -0.2 -1.6 -0.7 -2.3l-0.7 0.8z"
+        android:strokeWidth="0.5"
+        android:fillColor="?attr/iconColor"
+        android:strokeMiterLimit="10" />
+</vector>
diff --git a/res/drawable/ic_settings_bluetooth.xml b/res/drawable/ic_settings_bluetooth.xml
index 6b8623d..52127a8 100644
--- a/res/drawable/ic_settings_bluetooth.xml
+++ b/res/drawable/ic_settings_bluetooth.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,13 +14,14 @@
     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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0"
-        android:tint="@color/car_tint">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M35.41,15.41L24.0,4.0l-2.0,0.0l0.0,15.17L12.83,10.0 10.0,12.83 21.17,24.0 10.0,35.17 12.83,38.0 22.0,28.83L22.0,44.0l2.0,0.0l11.41,-11.41L26.83,24.0l8.58,-8.59zM26.0,11.66l3.76,3.76L26.0,19.17l0.0,-7.51zm3.76,20.93L26.0,36.34l0.0,-7.52l3.76,3.77z"/>
 </vector>
diff --git a/res/drawable/ic_settings_bluetooth_connected.xml b/res/drawable/ic_settings_bluetooth_connected.xml
index 43f29f4..537d239 100644
--- a/res/drawable/ic_settings_bluetooth_connected.xml
+++ b/res/drawable/ic_settings_bluetooth_connected.xml
@@ -1,25 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="20dp"
-        android:height="20dp"
-        android:viewportWidth="20.0"
-        android:viewportHeight="20.0">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="20.0"
+    android:viewportWidth="20.0">
     <path
-        android:pathData="M5.833,10L4.167,8.333L2.5,10l1.667,1.667L5.833,10zM14.758,6.425L10,1.667H9.167v6.325L5.342,4.167L4.167,5.342L8.825,10l-4.658,4.658l1.175,1.175l3.825,-3.825v6.325H10l4.758,-4.758L11.175,10C11.175,10 14.758,6.425 14.758,6.425zM10.833,4.858L12.4,6.425l-1.567,1.567V4.858zM12.4,13.575l-1.567,1.567v-3.133C10.833,12.008 12.4,13.575 12.4,13.575zM15.833,8.333L14.167,10l1.667,1.667L17.5,10L15.833,8.333z"
-        android:fillColor="#FFFFFF"/>
-</vector>
\ No newline at end of file
+        android:fillColor="?attr/iconColor"
+        android:pathData="M5.833,10L4.167,8.333L2.5,10l1.667,1.667L5.833,10zM14.758,6.425L10,1.667H9.167v6.325L5.342,4.167L4.167,5.342L8.825,10l-4.658,4.658l1.175,1.175l3.825,-3.825v6.325H10l4.758,-4.758L11.175,10C11.175,10 14.758,6.425 14.758,6.425zM10.833,4.858L12.4,6.425l-1.567,1.567V4.858zM12.4,13.575l-1.567,1.567v-3.133C10.833,12.008 12.4,13.575 12.4,13.575zM15.833,8.333L14.167,10l1.667,1.667L17.5,10L15.833,8.333z"/>
+</vector>
diff --git a/res/drawable/ic_settings_bluetooth_disabled.xml b/res/drawable/ic_settings_bluetooth_disabled.xml
index 0fbe506..4e4d150 100644
--- a/res/drawable/ic_settings_bluetooth_disabled.xml
+++ b/res/drawable/ic_settings_bluetooth_disabled.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M13,5.83l1.88,1.88 -1.6,1.6 1.41,1.41 3.02,-3.02L12,2h-1v5.03l2,2v-3.2zM5.41,4L4,5.41 10.59,12 5,17.59 6.41,19 11,14.41V22h1l4.29,-4.29 2.3,2.29L20,18.59 5.41,4zM13,18.17v-3.76l1.88,1.88L13,18.17z"/>
 </vector>
diff --git a/res/drawable/ic_settings_cellular.xml b/res/drawable/ic_settings_cellular.xml
new file mode 100644
index 0000000..18b28ff
--- /dev/null
+++ b/res/drawable/ic_settings_cellular.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M2,22h20V2z"/>
+</vector>
diff --git a/res/drawable/ic_settings_data_usage.xml b/res/drawable/ic_settings_data_usage.xml
new file mode 100644
index 0000000..54a9f50
--- /dev/null
+++ b/res/drawable/ic_settings_data_usage.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92V2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z"/>
+</vector>
diff --git a/res/drawable/ic_settings_date_time.xml b/res/drawable/ic_settings_date_time.xml
index 2202f8d..1cef06a 100644
--- a/res/drawable/ic_settings_date_time.xml
+++ b/res/drawable/ic_settings_date_time.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,16 +14,17 @@
     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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M11.99,2.0C6.47,2.0 2.0,6.48 2.0,12.0s4.47,10.0 9.99,10.0C17.52,22.0 22.0,17.52 22.0,12.0S17.52,2.0 11.99,2.0zM12.0,20.0c-4.42,0.0 -8.0,-3.58 -8.0,-8.0s3.58,-8.0 8.0,-8.0 8.0,3.58 8.0,8.0 -3.58,8.0 -8.0,8.0z"/>
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M12.5,7.0L11.0,7.0l0.0,6.0l5.25,3.1 0.75,-1.23 -4.5,-2.67z"/>
 </vector>
diff --git a/res/drawable/ic_settings_development.xml b/res/drawable/ic_settings_development.xml
new file mode 100644
index 0000000..6bf4307
--- /dev/null
+++ b/res/drawable/ic_settings_development.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="256.0"
+    android:viewportWidth="256.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="m66.641,197.718c-7.73,-7.73 -7.974,-8.747 -7.974,-33.186 0,-26.1 -1.346,-29.646 -12,-31.605 -9.22,-1.696 -8.063,-11.352 1.884,-15.729l8.783,-3.864 1.333,-26.241c1.262,-24.834 1.744,-26.593 8.986,-32.813 9.335,-8.017 21.681,-7.817 21.681,0.351 0,3.149 -2.974,6.723 -7.333,8.812 -7.171,3.437 -7.333,4.03 -7.333,26.776 0,16.737 -1.164,24.923 -4.151,29.188 -3.757,5.364 -3.757,6.489 0,11.853 3.037,4.336 4.151,12.604 4.151,30.805v24.879l8.145,2.839c6.138,2.14 7.945,4.241 7.333,8.528 -1.331,9.333 -13.894,9.016 -23.504,-0.593zM167.111,203.556c-4.355,-4.355 -1.269,-11.211 6.222,-13.823l8,-2.789v-24.879c0,-18.201 1.114,-26.469 4.151,-30.805 3.757,-5.364 3.757,-6.489 0,-11.853 -2.987,-4.264 -4.151,-12.451 -4.151,-29.188 0,-22.746 -0.162,-23.339 -7.333,-26.776 -4.359,-2.089 -7.333,-5.663 -7.333,-8.812 0,-8.168 12.346,-8.369 21.681,-0.351 7.242,6.22 7.724,7.979 8.986,32.813l1.333,26.241 8.783,3.864c9.947,4.377 11.104,14.033 1.884,15.729 -10.654,1.959 -12,5.505 -12,31.605 0,24.31 -0.279,25.491 -7.795,33.007 -7.235,7.235 -18.253,10.191 -22.427,6.017z"/>
+</vector>
diff --git a/res/drawable/ic_settings_display.xml b/res/drawable/ic_settings_display.xml
index f5f766e..670b286 100644
--- a/res/drawable/ic_settings_display.xml
+++ b/res/drawable/ic_settings_display.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,13 +14,14 @@
     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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M20.0,15.31L23.31,12.0 20.0,8.69L20.0,4.0l-4.69,0.0L12.0,0.69 8.69,4.0L4.0,4.0l0.0,4.69L0.69,12.0 4.0,15.31L4.0,20.0l4.69,0.0L12.0,23.31 15.31,20.0L20.0,20.0l0.0,-4.69zM12.0,18.0L12.0,6.0c3.31,0.0 6.0,2.69 6.0,6.0s-2.69,6.0 -6.0,6.0z"/>
 </vector>
diff --git a/res/drawable/ic_settings_gear.xml b/res/drawable/ic_settings_gear.xml
index 1a7db1e..3ef1034 100644
--- a/res/drawable/ic_settings_gear.xml
+++ b/res/drawable/ic_settings_gear.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,12 +14,14 @@
     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:height="24dp"
-    android:width="24dp"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
     android:viewportHeight="48.0"
-    android:viewportWidth="48.0"
-    android:tint="@color/car_tint" >
-    <path android:fillColor="#000000" android:pathData="M38.86,25.95c0.08,-0.64 0.14,-1.29 0.14,-1.95s-0.06,-1.31 -0.14,-1.95l4.23,-3.31c0.38,-0.3 0.49,-0.84 0.24,-1.28l-4,-6.93c-0.25,-0.43 -0.77,-0.61 -1.22,-0.43l-4.98,2.01c-1.03,-0.79 -2.16,-1.46 -3.38,-1.97L29,4.84c-0.09,-0.47 -0.5,-0.84 -1,-0.84h-8c-0.5,0 -0.91,0.37 -0.99,0.84l-0.75,5.3c-1.22,0.51 -2.35,1.17 -3.38,1.97L9.9,10.1c-0.45,-0.17 -0.97,0 -1.22,0.43l-4,6.93c-0.25,0.43 -0.14,0.97 0.24,1.28l4.22,3.31C9.06,22.69 9,23.34 9,24s0.06,1.31 0.14,1.95l-4.22,3.31c-0.38,0.3 -0.49,0.84 -0.24,1.28l4,6.93c0.25,0.43 0.77,0.61 1.22,0.43l4.98,-2.01c1.03,0.79 2.16,1.46 3.38,1.97l0.75,5.3c0.08,0.47 0.49,0.84 0.99,0.84h8c0.5,0 0.91,-0.37 0.99,-0.84l0.75,-5.3c1.22,-0.51 2.35,-1.17 3.38,-1.97l4.98,2.01c0.45,0.17 0.97,0 1.22,-0.43l4,-6.93c0.25,-0.43 0.14,-0.97 -0.24,-1.28l-4.22,-3.31zM24,31c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+    android:viewportWidth="48.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M38.86,25.95c0.08,-0.64 0.14,-1.29 0.14,-1.95s-0.06,-1.31 -0.14,-1.95l4.23,-3.31c0.38,-0.3 0.49,-0.84 0.24,-1.28l-4,-6.93c-0.25,-0.43 -0.77,-0.61 -1.22,-0.43l-4.98,2.01c-1.03,-0.79 -2.16,-1.46 -3.38,-1.97L29,4.84c-0.09,-0.47 -0.5,-0.84 -1,-0.84h-8c-0.5,0 -0.91,0.37 -0.99,0.84l-0.75,5.3c-1.22,0.51 -2.35,1.17 -3.38,1.97L9.9,10.1c-0.45,-0.17 -0.97,0 -1.22,0.43l-4,6.93c-0.25,0.43 -0.14,0.97 0.24,1.28l4.22,3.31C9.06,22.69 9,23.34 9,24s0.06,1.31 0.14,1.95l-4.22,3.31c-0.38,0.3 -0.49,0.84 -0.24,1.28l4,6.93c0.25,0.43 0.77,0.61 1.22,0.43l4.98,-2.01c1.03,0.79 2.16,1.46 3.38,1.97l0.75,5.3c0.08,0.47 0.49,0.84 0.99,0.84h8c0.5,0 0.91,-0.37 0.99,-0.84l0.75,-5.3c1.22,-0.51 2.35,-1.17 3.38,-1.97l4.98,2.01c0.45,0.17 0.97,0 1.22,-0.43l4,-6.93c0.25,-0.43 0.14,-0.97 -0.24,-1.28l-4.22,-3.31zM24,31c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
 </vector>
diff --git a/res/drawable/ic_settings_location.xml b/res/drawable/ic_settings_location.xml
new file mode 100644
index 0000000..83bbf87
--- /dev/null
+++ b/res/drawable/ic_settings_location.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
+</vector>
diff --git a/res/drawable/ic_settings_night_display.xml b/res/drawable/ic_settings_night_display.xml
index f1137fe..6b4d4b4 100644
--- a/res/drawable/ic_settings_night_display.xml
+++ b/res/drawable/ic_settings_night_display.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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
+         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,
@@ -15,9 +15,13 @@
     limitations under the License.
 -->
 
-<vector android:height="24dp" android:viewportHeight="44.0"
-    android:viewportWidth="44.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#212121" android:fillType="nonZero"
-        android:pathData="M26.5,40.33C28.42,40.33 30.26,40.04 32,39.49C24.56,37.16 19.17,30.21 19.17,22C19.17,13.79 24.56,6.84 32,4.51C30.26,3.96 28.42,3.67 26.5,3.67C16.38,3.67 8.17,11.88 8.17,22C8.17,32.12 16.38,40.33 26.5,40.33Z"
-        android:strokeColor="#00000000" android:strokeWidth="1"/>
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="44.0"
+    android:viewportWidth="44.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M26.5,40.33C28.42,40.33 30.26,40.04 32,39.49C24.56,37.16 19.17,30.21 19.17,22C19.17,13.79 24.56,6.84 32,4.51C30.26,3.96 28.42,3.67 26.5,3.67C16.38,3.67 8.17,11.88 8.17,22C8.17,32.12 16.38,40.33 26.5,40.33Z"/>
 </vector>
diff --git a/res/drawable/ic_settings_sound.xml b/res/drawable/ic_settings_sound.xml
index ca9a84d..2b8becb 100644
--- a/res/drawable/ic_settings_sound.xml
+++ b/res/drawable/ic_settings_sound.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,13 +14,14 @@
     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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M3.0,9.0l0.0,6.0l4.0,0.0l5.0,5.0L12.0,4.0L7.0,9.0L3.0,9.0zm13.5,3.0c0.0,-1.77 -1.02,-3.29 -2.5,-4.03l0.0,8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14.0,3.23l0.0,2.06c2.8,0.86 5.0,3.54 5.0,6.71s-2.11,5.85 -5.0,6.71l0.0,2.06c4.01,-0.91 7.0,-4.49 7.0,-8.77s-2.99,-7.86 -7.0,-8.77z"/>
 </vector>
diff --git a/res/drawable/ic_settings_wifi.xml b/res/drawable/ic_settings_wifi.xml
index eed2eff..c6324c7 100644
--- a/res/drawable/ic_settings_wifi.xml
+++ b/res/drawable/ic_settings_wifi.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,13 +14,14 @@
     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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="18.0"
-        android:viewportHeight="18.0"
-        android:tint="@color/car_tint">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="18.0"
+    android:viewportWidth="18.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M9.01,15.99l8.8,-10.96C17.47,4.77 14.08,2.0 9.0,2.0S0.53,4.7 0.19,5.03l8.8,10.96l0.02,0.0z"/>
 </vector>
diff --git a/res/drawable/ic_settings_wifi_disabled.xml b/res/drawable/ic_settings_wifi_disabled.xml
index 1b2ac4e..b4bd202 100644
--- a/res/drawable/ic_settings_wifi_disabled.xml
+++ b/res/drawable/ic_settings_wifi_disabled.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"/>
 </vector>
diff --git a/res/drawable/ic_storage.xml b/res/drawable/ic_storage.xml
new file mode 100644
index 0000000..e02e7f0
--- /dev/null
+++ b/res/drawable/ic_storage.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/icon_size"
+        android:height="@dimen/icon_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M3,20h18v-4H3V20zM5,17h2v2H5V17zM3,4v4h18V4H3zM7,7H5V5h2V7zM3,14h18v-4H3V14zM5,11h2v2H5V11z"/>
+</vector>
diff --git a/res/drawable/ic_storage_apps.xml b/res/drawable/ic_storage_apps.xml
new file mode 100644
index 0000000..5d080c8
--- /dev/null
+++ b/res/drawable/ic_storage_apps.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/icon_size"
+        android:height="@dimen/icon_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M6,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM16,6c0,1.1 0.9,2 2,2s2,-0.9 2,-2 -0.9,-2 -2,-2 -2,0.9 -2,2zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
+</vector>
diff --git a/res/drawable/ic_sync.png b/res/drawable/ic_sync.png
new file mode 100644
index 0000000..f5e19bd
--- /dev/null
+++ b/res/drawable/ic_sync.png
Binary files differ
diff --git a/res/drawable/ic_sync_anim.xml b/res/drawable/ic_sync_anim.xml
new file mode 100644
index 0000000..b5f5433
--- /dev/null
+++ b/res/drawable/ic_sync_anim.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
+                 android:drawable="@drawable/ic_sync"
+                 android:pivotX="50%"
+                 android:pivotY="50%"/>
diff --git a/res/drawable/ic_sync_problem.xml b/res/drawable/ic_sync_problem.xml
new file mode 100644
index 0000000..4e0c72b
--- /dev/null
+++ b/res/drawable/ic_sync_problem.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright 2018 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="32dp"
+        android:height="32dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M3,12c0,2.21 0.91,4.2 2.36,5.64L3,20h6v-6l-2.24,2.24C5.68,15.15 5,13.66 5,12c0,-2.61 1.67,-4.83 4,-5.65L9,4.26C5.55,5.15 3,8.27 3,12zM11,17h2v-2h-2v2zM21,4h-6v6l2.24,-2.24C18.32,8.85 19,10.34 19,12c0,2.61 -1.67,4.83 -4,5.65v2.09c3.45,-0.89 6,-4.01 6,-7.74 0,-2.21 -0.91,-4.2 -2.36,-5.64L21,4zM11,13h2L13,7h-2v6z"/>
+</vector>
diff --git a/res/drawable/ic_system_update.xml b/res/drawable/ic_system_update.xml
index ead6ff0..69ccc06 100644
--- a/res/drawable/ic_system_update.xml
+++ b/res/drawable/ic_system_update.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -13,13 +14,14 @@
     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.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14zM16,13h-3L13,8h-2v5L8,13l4,4 4,-4z"
-        android:fillColor="#FFFFFFFF"/>
+        android:fillColor="?attr/iconColor"
+        android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14zM16,13h-3L13,8h-2v5L8,13l4,4 4,-4z"/>
 </vector>
diff --git a/res/drawable/ic_translate.xml b/res/drawable/ic_translate.xml
new file mode 100644
index 0000000..6e22fc5
--- /dev/null
+++ b/res/drawable/ic_translate.xml
@@ -0,0 +1,26 @@
+<!--
+    Copyright 2018 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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/>
+</vector>
diff --git a/res/drawable/ic_user.xml b/res/drawable/ic_user.xml
index bfce534..3906c00 100644
--- a/res/drawable/ic_user.xml
+++ b/res/drawable/ic_user.xml
@@ -1,26 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@color/car_tint">
+    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="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="?attr/iconColor"
         android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
 </vector>
diff --git a/res/drawable/ic_user_add.xml b/res/drawable/ic_user_add.xml
new file mode 100644
index 0000000..213b343
--- /dev/null
+++ b/res/drawable/ic_user_add.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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="@dimen/touch_target_size"
+    android:height="@dimen/touch_target_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/userSwitcherAddIconColor"
+        android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/res/drawable/ic_wifi_signal_0.xml b/res/drawable/ic_wifi_signal_0.xml
index 9f37b04..363edbb 100644
--- a/res/drawable/ic_wifi_signal_0.xml
+++ b/res/drawable/ic_wifi_signal_0.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
 
-        http://www.apache.org/licenses/LICENSE-2.0
+         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,
@@ -14,13 +14,15 @@
     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="26dp"
-  android:height="24dp"
-  android:viewportWidth="26"
-  android:viewportHeight="24">
-  <path
-    android:fillAlpha="0.3"
-    android:fillColor="?attr/WifiSignalColor"
-    android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
-</vector>
\ No newline at end of file
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="26dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="26">
+    <path
+        android:fillAlpha="0.3"
+        android:fillColor="?attr/wifiSignalColor"
+        android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/res/drawable/ic_wifi_signal_1.xml b/res/drawable/ic_wifi_signal_1.xml
index 4b9139e..0c31c22 100644
--- a/res/drawable/ic_wifi_signal_1.xml
+++ b/res/drawable/ic_wifi_signal_1.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
 
-        http://www.apache.org/licenses/LICENSE-2.0
+         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,
@@ -14,16 +14,18 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="26dp"
     android:height="24dp"
-    android:viewportWidth="26"
-    android:viewportHeight="24">
+    android:viewportHeight="24"
+    android:viewportWidth="26">
     <path
         android:fillAlpha="0.3"
-        android:fillColor="?attr/WifiSignalColor"
+        android:fillColor="?attr/wifiSignalColor"
         android:pathData="M13.1,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.5,6.5L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0z"/>
     <path
-        android:fillColor="?attr/WifiSignalColor"
+        android:fillColor="?attr/wifiSignalColor"
         android:pathData="M13.1,22.0l5.5,-6.8c-0.2,-0.2 -2.3,-1.9 -5.5,-1.9s-5.3,1.8 -5.5,1.9L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0z"/>
 </vector>
diff --git a/res/drawable/ic_wifi_signal_2.xml b/res/drawable/ic_wifi_signal_2.xml
index 66e1d7c..f8466d9 100644
--- a/res/drawable/ic_wifi_signal_2.xml
+++ b/res/drawable/ic_wifi_signal_2.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
 
-        http://www.apache.org/licenses/LICENSE-2.0
+         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,
@@ -14,16 +14,18 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="26dp"
     android:height="24dp"
-    android:viewportWidth="26"
-    android:viewportHeight="24">
+    android:viewportHeight="24"
+    android:viewportWidth="26">
     <path
         android:fillAlpha="0.3"
-        android:fillColor="?attr/WifiSignalColor"
+        android:fillColor="?attr/wifiSignalColor"
         android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
     <path
-        android:fillColor="?attr/WifiSignalColor"
+        android:fillColor="?attr/wifiSignalColor"
         android:pathData="M13.0,22.0l7.6,-9.4C20.3,12.4 17.4,10.0 13.0,10.0s-7.3,2.4 -7.6,2.7L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
 </vector>
diff --git a/res/drawable/ic_wifi_signal_3.xml b/res/drawable/ic_wifi_signal_3.xml
index f37fa59..2f0d8a4 100644
--- a/res/drawable/ic_wifi_signal_3.xml
+++ b/res/drawable/ic_wifi_signal_3.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
 
-        http://www.apache.org/licenses/LICENSE-2.0
+         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,
@@ -14,16 +14,18 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="26dp"
     android:height="24dp"
-    android:viewportWidth="26"
-    android:viewportHeight="24">
+    android:viewportHeight="24"
+    android:viewportWidth="26">
     <path
         android:fillAlpha="0.3"
-        android:fillColor="?attr/WifiSignalColor"
+        android:fillColor="?attr/wifiSignalColor"
         android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
     <path
-        android:fillColor="?attr/WifiSignalColor"
+        android:fillColor="?attr/wifiSignalColor"
         android:pathData="M13.0,22.0l9.2,-11.4c-0.4,-0.3 -3.9,-3.2 -9.2,-3.2s-8.9,3.0 -9.2,3.2L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
 </vector>
diff --git a/res/drawable/ic_wifi_signal_4.xml b/res/drawable/ic_wifi_signal_4.xml
index 1cc86b6..50d8e04 100644
--- a/res/drawable/ic_wifi_signal_4.xml
+++ b/res/drawable/ic_wifi_signal_4.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
 
-        http://www.apache.org/licenses/LICENSE-2.0
+         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,
@@ -14,12 +14,14 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="26dp"
     android:height="24dp"
-    android:viewportWidth="26"
-    android:viewportHeight="24">
+    android:viewportHeight="24"
+    android:viewportWidth="26">
     <path
-        android:fillColor="?attr/WifiSignalColor"
+        android:fillColor="?attr/wifiSignalColor"
         android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
 </vector>
diff --git a/res/drawable/ic_wifi_signal_lock.xml b/res/drawable/ic_wifi_signal_lock.xml
index cf420d4..a5ea3fe 100644
--- a/res/drawable/ic_wifi_signal_lock.xml
+++ b/res/drawable/ic_wifi_signal_lock.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2017 The Android Open Source Project
+    Copyright 2017 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
 
-        http://www.apache.org/licenses/LICENSE-2.0
+         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,
@@ -14,16 +14,18 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"
-    android:viewportWidth="72"
-    android:viewportHeight="72">
+    android:viewportHeight="72"
+    android:viewportWidth="72">
     <group
         android:translateX="52.0"
         android:translateY="42.0">
         <path
-            android:fillColor="?attr/WifiSignalColor"
+            android:fillColor="?attr/wifiSignalColor"
             android:pathData="M18.0,8.0l-1.0,0.0L17.0,6.0c0.0,-2.76 -2.24,-5.0 -5.0,-5.0S7.0,3.24 7.0,6.0l0.0,2.0L6.0,8.0c-1.1,0.0 -2.0,0.9 -2.0,2.0l0.0,10.0c0.0,1.0 0.9,2.0 2.0,2.0l12.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L20.0,10.0c0.0,-1.1 -0.9,-2.0 -2.0,-2.0zm-6.0,9.0c-1.1,0.0 -2.0,-0.9 -2.0,-2.0s0.9,-2.0 2.0,-2.0 2.0,0.9 2.0,2.0 -0.9,2.0 -2.0,2.0zm3.1,-9.0L8.9,8.0L8.9,6.0c0.0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0.0 3.1,1.39 3.1,3.1l0.0,2.0z"/>
     </group>
-</vector>
\ No newline at end of file
+</vector>
diff --git a/res/drawable/ic_wifi_tethering.xml b/res/drawable/ic_wifi_tethering.xml
new file mode 100644
index 0000000..402a69b
--- /dev/null
+++ b/res/drawable/ic_wifi_tethering.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/icon_size"
+        android:height="@dimen/icon_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13a6,6 0,0 0,-6.75 -5.95c-2.62,0.32 -4.78,2.41 -5.18,5.02 -0.32,2.14 0.49,4.11 1.92,5.39 0.48,0.43 1.24,0.33 1.56,-0.23 0.24,-0.42 0.14,-0.94 -0.22,-1.26a3.99,3.99 0,0 1,-1.22 -3.94,3.954 3.954,0 0,1 2.9,-2.91A4.007,4.007 0,0 1,16 13c0,1.18 -0.51,2.23 -1.33,2.96 -0.36,0.33 -0.47,0.85 -0.23,1.27 0.31,0.54 1.04,0.69 1.5,0.28A5.97,5.97 0,0 0,18 13zM10.83,3.07c-4.62,0.52 -8.35,4.33 -8.78,8.96a9.966,9.966 0,0 0,4.02 9.01c0.48,0.35 1.16,0.2 1.46,-0.31 0.25,-0.43 0.14,-0.99 -0.26,-1.29 -2.28,-1.69 -3.65,-4.55 -3.16,-7.7 0.54,-3.5 3.46,-6.29 6.98,-6.68C15.91,4.51 20,8.28 20,13c0,2.65 -1.29,4.98 -3.27,6.44 -0.4,0.3 -0.51,0.85 -0.26,1.29 0.3,0.52 0.98,0.66 1.46,0.31A9.96,9.96 0,0 0,22 13c0,-5.91 -5.13,-10.62 -11.17,-9.93z"
+        android:fillColor="?attr/iconColor"/>
+</vector>
diff --git a/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml b/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
index 6491f8f..c9ef48f 100644
--- a/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
+++ b/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
@@ -1,28 +1,30 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!--
+    Copyright 2017 The Android Open Source Project
 
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
+    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
+         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.
+    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.
 -->
 
 <!-- Variant of progress_indeterminate_horizontal_material in frameworks/base/core/res, which
      draws the whole height of the progress bar instead having blank space above and below the
      bar. -->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:drawable="@drawable/vector_drawable_progress_indeterminate_horizontal_trimmed" >
+<animated-vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/vector_drawable_progress_indeterminate_horizontal_trimmed">
     <target
         android:name="rect2_grp"
-        android:animation="@*android:anim/progress_indeterminate_horizontal_rect2" />
+        android:animation="@*android:anim/progress_indeterminate_horizontal_rect2"/>
     <target
         android:name="rect1_grp"
-        android:animation="@*android:anim/progress_indeterminate_horizontal_rect1" />
+        android:animation="@*android:anim/progress_indeterminate_horizontal_rect1"/>
 </animated-vector>
diff --git a/res/drawable/rectangle_ripple_mask.xml b/res/drawable/rectangle_ripple_mask.xml
index 69eaf8b..6eb2a75 100644
--- a/res/drawable/rectangle_ripple_mask.xml
+++ b/res/drawable/rectangle_ripple_mask.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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
+         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,
@@ -15,8 +15,9 @@
     limitations under the License.
 -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
-  <corners android:radius="@dimen/car_radius_1" />
-  <solid android:color="@android:color/white" />
-</shape>
\ No newline at end of file
+    <corners android:radius="@dimen/button_ripple_radius"/>
+    <solid android:color="@android:color/white"/>
+</shape>
diff --git a/res/drawable/regulatory_info.png b/res/drawable/regulatory_info.png
new file mode 100644
index 0000000..65de26c
--- /dev/null
+++ b/res/drawable/regulatory_info.png
Binary files differ
diff --git a/res/drawable/spinner_background.xml b/res/drawable/spinner_background.xml
deleted file mode 100644
index 8fac0f9..0000000
--- a/res/drawable/spinner_background.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item>
-        <layer-list>
-            <item android:gravity="right" android:drawable="@drawable/ic_arrow_drop_down" />
-        </layer-list>
-    </item>
-
-</selector>
\ No newline at end of file
diff --git a/res/drawable/suw_button_ripple_bg.xml b/res/drawable/suw_button_ripple_bg.xml
deleted file mode 100644
index bc49f56..0000000
--- a/res/drawable/suw_button_ripple_bg.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 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.
--->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/car_card_ripple_background">
-</ripple>
diff --git a/res/drawable/user_add_circle.xml b/res/drawable/user_add_circle.xml
new file mode 100644
index 0000000..0fd0ebc
--- /dev/null
+++ b/res/drawable/user_add_circle.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="?attr/userSwitcherAddIconBackgroundColor"/>
+            <size
+                android:width="@dimen/user_switcher_image_avatar_size"
+                android:height="@dimen/user_switcher_image_avatar_size"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_user_add"
+        android:gravity="center"/>
+</layer-list>
diff --git a/res/drawable/user_avatar_bg_circle.xml b/res/drawable/user_avatar_bg_circle.xml
new file mode 100644
index 0000000..52a81cc
--- /dev/null
+++ b/res/drawable/user_avatar_bg_circle.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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"
+    android:shape="oval">
+    <stroke
+        android:width="@dimen/user_switcher_current_user_circle_stroke_width"
+        android:color="?attr/userSwitcherCurrentUserColor"/>
+    <size
+        android:width="@dimen/user_switcher_current_user_circle_width"
+        android:height="@dimen/user_switcher_current_user_circle_width"/>
+</shape>
diff --git a/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml b/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
index 2d65ad3..b14a04a 100644
--- a/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
+++ b/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
@@ -1,53 +1,55 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!--
+    Copyright 2017 The Android Open Source Project
 
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
+    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
+         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.
+    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.
 -->
 
 <!-- Variant of vector_drawable_progress_indeterminate_horizontal in frameworks/base/core/res, which
      draws the whole height of the progress bar instead having blank space above and below the
      bar. -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="10dp"
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="360dp"
+    android:height="10dp"
     android:viewportHeight="10"
-    android:viewportWidth="360" >
+    android:viewportWidth="360">
     <group
         android:name="progress_group"
         android:translateX="180"
-        android:translateY="5" >
+        android:translateY="5">
         <path
             android:name="background_track"
-            android:pathData="M -180.0,-5.0 l 360.0,0 l 0,10.0 l -360.0,0 Z"
+            android:fillAlpha="?android:attr/disabledAlpha"
             android:fillColor="?android:attr/colorControlActivated"
-            android:fillAlpha="?android:attr/disabledAlpha"/>
+            android:pathData="M -180.0,-5.0 l 360.0,0 l 0,10.0 l -360.0,0 Z"/>
         <group
             android:name="rect2_grp"
-            android:translateX="-197.60001"
-            android:scaleX="0.1" >
+            android:scaleX="0.1"
+            android:translateX="-197.60001">
             <path
                 android:name="rect2"
-                android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"
-                android:fillColor="?android:attr/colorControlActivated" />
+                android:fillColor="?android:attr/colorControlActivated"
+                android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"/>
         </group>
         <group
             android:name="rect1_grp"
-            android:translateX="-522.59998"
-            android:scaleX="0.1" >
+            android:scaleX="0.1"
+            android:translateX="-522.59998">
             <path
                 android:name="rect1"
-                android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"
-                android:fillColor="?android:attr/colorControlActivated" />
+                android:fillColor="?android:attr/colorControlActivated"
+                android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"/>
         </group>
     </group>
 </vector>
diff --git a/res/drawable/wifi_signal.xml b/res/drawable/wifi_signal.xml
index 884aefc..80aef79 100644
--- a/res/drawable/wifi_signal.xml
+++ b/res/drawable/wifi_signal.xml
@@ -1,41 +1,64 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!--
+    Copyright 2017 The Android Open Source Project
 
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
+    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
+         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.
+    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.
 -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:settings="http://schemas.android.com/apk/res/com.android.car.settings">
+<selector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res/com.android.car.settings">
     <item settings:state_encrypted="true">
         <layer-list>
             <item>
                 <level-list>
-                    <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
-                    <item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
-                    <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
-                    <item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
+                    <item
+                        android:drawable="@drawable/ic_wifi_signal_0"
+                        android:maxLevel="0"/>
+                    <item
+                        android:drawable="@drawable/ic_wifi_signal_1"
+                        android:maxLevel="1"/>
+                    <item
+                        android:drawable="@drawable/ic_wifi_signal_2"
+                        android:maxLevel="2"/>
+                    <item
+                        android:drawable="@drawable/ic_wifi_signal_3"
+                        android:maxLevel="3"/>
+                    <item
+                        android:drawable="@drawable/ic_wifi_signal_4"
+                        android:maxLevel="4"/>
                 </level-list>
             </item>
-            <item android:drawable="@drawable/ic_wifi_signal_lock" />
+            <item android:drawable="@drawable/ic_wifi_signal_lock"/>
         </layer-list>
     </item>
     <item settings:state_encrypted="false">
         <level-list>
-            <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
-            <item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
-            <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
-            <item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
+            <item
+                android:drawable="@drawable/ic_wifi_signal_0"
+                android:maxLevel="0"/>
+            <item
+                android:drawable="@drawable/ic_wifi_signal_1"
+                android:maxLevel="1"/>
+            <item
+                android:drawable="@drawable/ic_wifi_signal_2"
+                android:maxLevel="2"/>
+            <item
+                android:drawable="@drawable/ic_wifi_signal_3"
+                android:maxLevel="3"/>
+            <item
+                android:drawable="@drawable/ic_wifi_signal_4"
+                android:maxLevel="4"/>
         </level-list>
     </item>
 </selector>
-
diff --git a/res/layout/action_bar.xml b/res/layout/action_bar.xml
index a06c991..f3d3d91 100644
--- a/res/layout/action_bar.xml
+++ b/res/layout/action_bar.xml
@@ -1,45 +1,60 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<RelativeLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/car_app_bar_height"
-        android:gravity="end|center_vertical" >
+    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.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
     <FrameLayout
         android:id="@+id/action_bar_icon_container"
-        android:layout_width="@dimen/car_margin"
-        android:layout_height="@dimen/car_app_bar_height"
-        android:layout_alignParentStart="true"
-        android:background="@drawable/button_ripple_bg">
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/button_ripple_bg"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/start_margin"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
         <ImageView
             android:id="@+id/back_button"
-            style="@style/ListIcon.ActionBar"/>
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:layout_gravity="center"
+            android:scaleType="fitCenter"
+            android:src="@drawable/ic_arrow_back"/>
     </FrameLayout>
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/start_margin"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@*android:dimen/action_bar_margin_start"/>
+
     <TextView
         android:id="@+id/title"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/car_margin"
-        android:layout_marginTop="@dimen/optical_center_offset"
-        android:textAppearance="@style/TextAppearance.Car.Title2"
-        android:layout_gravity="center_vertical"
-        android:layout_centerVertical="true"
-        android:layout_alignParentStart="true"
+        android:ellipsize="end"
         android:maxLines="1"
-        android:ellipsize="end"/>
-</RelativeLayout>
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/start_margin"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/action_bar_quick_settings.xml b/res/layout/action_bar_quick_settings.xml
index 40de003..193b199 100644
--- a/res/layout/action_bar_quick_settings.xml
+++ b/res/layout/action_bar_quick_settings.xml
@@ -1,106 +1,90 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 The Android Open Source Project
 
-<RelativeLayout
+    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.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/car_app_bar_height"
-    android:layout_gravity="top">
+    android:layout_height="match_parent">
 
     <FrameLayout
         android:id="@+id/action_bar_icon_container"
-        android:layout_width="@dimen/car_margin"
-        android:layout_height="match_parent"
-        android:layout_alignParentStart="true"
-        android:background="@drawable/button_ripple_bg">
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/button_ripple_bg"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/start_margin"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
         <ImageView
             android:id="@+id/exit_button"
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
             android:layout_gravity="center"
-            android:src="@drawable/ic_close"
-            style="@style/ListIcon.ActionBar"/>
+            android:scaleType="fitCenter"
+            android:src="@drawable/ic_close"/>
     </FrameLayout>
 
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/start_margin"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@*android:dimen/action_bar_margin_start"/>
+
     <TextView
         android:id="@+id/title"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_marginEnd="@dimen/car_keyline_1"
-        android:layout_toEndOf="@id/action_bar_icon_container"
-        android:gravity="center_vertical"
-        android:textAppearance="@style/TextAppearance.Car.Title2"/>
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/start_margin"
+        app:layout_constraintTop_toTopOf="parent"/>
 
-    <LinearLayout
-        android:id="@+id/button_container"
+    <Button
+        android:id="@+id/user_switcher_btn"
+        style="@*android:style/ActionBarButton"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_alignParentEnd="true"
-        android:layout_marginEnd="@dimen/car_keyline_1"
-        android:orientation="horizontal">
+        android:layout_height="0dp"
+        android:drawableStart="@drawable/ic_user"
+        android:drawableTint="?android:attr/textColorPrimary"
+        android:textColor="?android:attr/textColorPrimary"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/full_settings_btn"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toEndOf="@+id/title"
+        app:layout_constraintTop_toTopOf="parent"/>
 
-        <LinearLayout
-            android:id="@+id/user_switcher_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="center_vertical"
-            android:layout_marginEnd="@dimen/car_padding_4"
-            android:background="@drawable/button_ripple_bg"
-            style="@style/Widget.Car.Settings.ActionBar.Button.Borderless.Colored"
-            android:orientation="horizontal">
-            <ImageView
-                android:id="@+id/user_icon"
-                android:src="@drawable/ic_user"
-                android:background="@null"
-                android:layout_width="@dimen/car_primary_icon_size"
-                android:layout_height="@dimen/car_primary_icon_size"
-                android:layout_centerVertical="true"
-                android:gravity="center_vertical"
-                android:scaleType="fitCenter" />
-            <TextView
-                android:id="@+id/user_switcher_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:background="@null"
-                android:textAppearance="@style/TextAppearance.Car.Label1"
-                android:layout_marginStart="@dimen/car_padding_2"
-                android:layout_marginEnd="@dimen/car_padding_2"/>
-        </LinearLayout>
-        <LinearLayout
-            android:id="@+id/full_setting_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="center_vertical"
-            android:layout_marginEnd="@dimen/car_padding_4"
-            android:background="@drawable/button_ripple_bg"
-            style="@style/Widget.Car.Settings.ActionBar.Button.Borderless.Colored"
-            android:orientation="horizontal">
-            <ImageView
-                android:src="@drawable/ic_settings_gear"
-                android:background="@null"
-                style="@style/ListIcon"/>
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:text="@string/more_settings_label"
-                android:textAppearance="@style/TextAppearance.Car.Label1"
-                android:background="@null"
-                android:layout_marginStart="@dimen/car_padding_2"
-                android:layout_marginEnd="@dimen/car_padding_2"/>
-        </LinearLayout>
-    </LinearLayout>
-</RelativeLayout>
+    <Button
+        android:id="@+id/full_settings_btn"
+        style="@*android:style/ActionBarButton"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:layout_marginHorizontal="@*android:dimen/action_bar_button_margin"
+        android:drawableStart="@drawable/ic_settings_gear"
+        android:text="@string/more_settings_label"
+        android:drawableTint="?android:attr/textColorPrimary"
+        android:textColor="?android:attr/textColorPrimary"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/user_switcher_btn"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/action_bar_with_button.xml b/res/layout/action_bar_with_button.xml
index 1227d9c..96864be 100644
--- a/res/layout/action_bar_with_button.xml
+++ b/res/layout/action_bar_with_button.xml
@@ -1,77 +1,84 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<RelativeLayout
+    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.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/car_app_bar_height">
+    android:layout_height="match_parent">
 
     <FrameLayout
         android:id="@+id/action_bar_icon_container"
-        android:layout_width="@dimen/car_margin"
-        android:layout_height="match_parent"
-        android:layout_alignParentStart="true"
-        android:background="@drawable/button_ripple_bg">
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/button_ripple_bg"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/start_margin"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
         <ImageView
             android:id="@+id/back_button"
-            style="@style/ListIcon.ActionBar"/>
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:layout_gravity="center"
+            android:scaleType="fitCenter"
+            android:src="@drawable/ic_arrow_back"/>
     </FrameLayout>
 
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/start_margin"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@*android:dimen/action_bar_margin_start"/>
+
     <TextView
         android:id="@+id/title"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_marginEnd="@dimen/car_keyline_1"
-        android:layout_toEndOf="@id/action_bar_icon_container"
-        android:gravity="center_vertical"
-        android:textAppearance="@style/TextAppearance.Car.Title2"/>
-
-    <LinearLayout
-        android:id="@+id/button_container"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_alignParentEnd="true"
-        android:layout_marginEnd="@dimen/car_keyline_1"
-        android:orientation="horizontal">
-
-        <Button
-            android:id="@+id/action_button2"
-            android:visibility="gone"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            style="?android:attr/borderlessButtonStyle"
-            android:layout_marginEnd="@dimen/car_padding_3"/>
-
-        <Button
-            android:id="@+id/action_button1"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            style="?android:attr/borderlessButtonStyle"
-            android:layout_marginEnd="@dimen/action_bar_end_widget_margin_end" />
-    </LinearLayout>
-
-    <ProgressBar
-        android:id="@+id/progress_bar"
-        style="@style/Widget.Car.ProgressBar.Horizontal"
-        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/start_margin"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+    <Button
+        android:id="@+id/action_button2"
+        style="@*android:style/ActionBarButton"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
         android:visibility="gone"
-        android:indeterminate="true"/>
-</RelativeLayout>
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/action_button1"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toEndOf="@+id/title"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+    <Button
+        android:id="@+id/action_button1"
+        style="@*android:style/ActionBarButton"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:layout_marginHorizontal="@*android:dimen/action_bar_button_margin"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/action_button2"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/action_bar_with_toggle.xml b/res/layout/action_bar_with_toggle.xml
index ec03e99..d18a20e 100644
--- a/res/layout/action_bar_with_toggle.xml
+++ b/res/layout/action_bar_with_toggle.xml
@@ -1,55 +1,75 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!--
+    Copyright 2017 The Android Open Source Project
 
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
+    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
+         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.
+    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.
 -->
 
-<RelativeLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/car_app_bar_height"
-        android:gravity="center_vertical" >
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
     <FrameLayout
         android:id="@+id/action_bar_icon_container"
-        android:layout_width="@dimen/car_margin"
-        android:layout_height="@dimen/car_app_bar_height"
-        android:layout_alignParentStart="true"
-        android:background="@drawable/button_ripple_bg">
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/button_ripple_bg"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/start_margin"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
         <ImageView
             android:id="@+id/back_button"
-            style="@style/ListIcon.ActionBar"/>
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:layout_gravity="center"
+            android:scaleType="fitCenter"
+            android:src="@drawable/ic_arrow_back"/>
     </FrameLayout>
 
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/start_margin"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@*android:dimen/action_bar_margin_start"/>
+
     <TextView
         android:id="@+id/title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="@style/TextAppearance.Car.Title2"
-        android:layout_marginTop="@dimen/optical_center_offset"
-        android:layout_gravity="center_vertical"
-        android:layout_centerVertical="true"
-        android:layout_alignParentStart="true"
-        android:layout_marginStart="@dimen/car_margin"
+        android:ellipsize="end"
         android:maxLines="1"
-        android:ellipsize="end"/>
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/start_margin"
+        app:layout_constraintTop_toTopOf="parent"/>
 
-    <Switch
-        android:id="@+id/toggle_switch"
-        android:background="@null"
-        style="@style/ListSwitch"
-        android:layout_centerVertical="true"
-        android:layout_alignParentEnd="true"
-        android:layout_marginEnd="@dimen/action_bar_end_widget_margin_end" />
-
-</RelativeLayout>
+    <FrameLayout
+        android:layout_width="@*android:dimen/action_bar_margin_end"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+        <Switch
+            android:id="@+id/toggle_switch"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:paddingStart="@*android:dimen/action_bar_toggle_internal_padding"
+            android:paddingEnd="@*android:dimen/action_bar_toggle_internal_padding"
+            android:background="@null"/>
+    </FrameLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/add_wifi.xml b/res/layout/add_wifi.xml
deleted file mode 100644
index 3e6c78b..0000000
--- a/res/layout/add_wifi.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical" >
-    <ViewSwitcher
-        android:id="@+id/wifi_name"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-        <TextView
-            android:id="@+id/wifi_name_display"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/TextAppearance.Car.Body1.SingleLine" />
-        <android.support.design.widget.TextInputLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:theme="@style/Theme.AppCompat.Light">
-            <EditText
-                android:id="@+id/wifi_name_input"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:focusableInTouchMode="true"
-                style="@style/TextAppearance.Car.Body1"
-                android:hint="@string/wifi_ssid_hint" />
-        </android.support.design.widget.TextInputLayout>
-    </ViewSwitcher>
-    <android.support.design.widget.TextInputLayout
-        android:id="@+id/wifi_password_wrapper"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:theme="@style/Theme.AppCompat.Light">
-        <EditText
-            android:id="@+id/wifi_password"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:focusableInTouchMode="true"
-            style="@style/TextAppearance.Car.Body1"
-            android:inputType="textPassword"
-            android:hint="@string/wifi_password" />
-    </android.support.design.widget.TextInputLayout>
-    <Button
-        android:id="@+id/wifi_connect"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/wifi_setup_connect" />
-</LinearLayout>
diff --git a/res/layout/app_compat_activity.xml b/res/layout/app_compat_activity.xml
deleted file mode 100644
index 2ce17b8..0000000
--- a/res/layout/app_compat_activity.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-    <android.support.v7.widget.Toolbar
-        android:id="@+id/toolbar"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/car_app_bar_height"
-        app:contentInsetStart="0dp"
-        style="@style/ActionBarStyle.Car" />
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/car_list_divider_height"
-        android:background="@color/car_list_divider"/>
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <FrameLayout
-            android:id="@+id/fragment_container"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
-        <!-- set the text clickable to true so that it blocks touch event -->
-        <TextView
-            android:id="@+id/restricted_message"
-            android:gravity="center"
-            android:background="@color/car_grey_1000"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:text="@string/restricted_while_driving"
-            android:clickable="true"
-            android:visibility="gone"
-            android:textAppearance="@style/TextAppearance.Car.Body1.Light" />
-    </FrameLayout>
-</LinearLayout>
diff --git a/res/layout/bluetooth_list.xml b/res/layout/bluetooth_list.xml
deleted file mode 100644
index 35c259e..0000000
--- a/res/layout/bluetooth_list.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical" >
-    <ProgressBar
-        android:id="@+id/bt_search_progress"
-        android:visibility="gone"
-        style="@style/TrimmedHorizontalProgressBar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:indeterminate="true" />
-    <ViewSwitcher
-        android:id="@+id/view_switcher"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <android.support.v4.widget.SwipeRefreshLayout
-            android:id="@+id/swiperefresh"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
-            <androidx.car.widget.PagedListView
-                android:id="@+id/list"
-                style="@style/SettingList"
-                app:showPagedListViewDivider="false"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent" />
-        </android.support.v4.widget.SwipeRefreshLayout>
-        <TextView
-            android:id="@+id/bt_message"
-            android:layout_gravity="center"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/bluetooth_disabled"
-            style="@style/TextAppearance.Car.Body1" />
-    </ViewSwitcher>
-</LinearLayout>
diff --git a/res/layout/bluetooth_pin_confirm.xml b/res/layout/bluetooth_pin_confirm.xml
index 4a9a2be..6d510d0 100644
--- a/res/layout/bluetooth_pin_confirm.xml
+++ b/res/layout/bluetooth_pin_confirm.xml
@@ -1,91 +1,95 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 2017, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
 -->
 
 <ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="match_parent"
-    android:layout_width="match_parent">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
     <LinearLayout
-        android:layout_marginStart="@dimen/car_keyline_1"
-        android:layout_marginEnd="@dimen/car_keyline_1"
-        android:layout_height="match_parent"
         android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="@dimen/bluetooth_pin_dialog_margin_end"
+        android:layout_marginStart="@dimen/bluetooth_pin_dialog_margin_start"
         android:orientation="vertical">
 
         <RelativeLayout
-            android:layout_height="@dimen/car_double_line_list_item_height"
-            android:layout_width="match_parent" >
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bluetooth_pin_dialog_section_height">
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:orientation="vertical"
+                android:layout_alignParentStart="true"
                 android:layout_centerVertical="true"
                 android:gravity="center_vertical"
-                android:layout_alignParentStart="true">
+                android:orientation="vertical">
                 <TextView
                     android:id="@+id/pairing_caption"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
+                    android:ellipsize="end"
+                    android:maxLines="1"
                     android:text="@string/bluetooth_pairing_key_msg"
-                    android:visibility="gone"
-                    style="@style/TextAppearance.Car.Body2.SingleLine" />
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:visibility="gone"/>
                 <TextView
                     android:id="@+id/pairing_subhead"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:visibility="gone"
-                    style="@style/TextAppearance.Car.Body1.SingleLine"
-                    android:layout_marginTop="@dimen/car_padding_1" />
+                    android:layout_marginTop="@dimen/bluetooth_pin_dialog_subtext_margin_top"
+                    android:ellipsize="marquee"
+                    android:maxLines="1"
+                    android:textAppearance="?android:attr/textAppearanceLarge"
+                    android:visibility="gone"/>
             </LinearLayout>
         </RelativeLayout>
 
         <TextView
             android:id="@+id/pairing_code_message"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/car_single_line_list_item_height"
+            android:layout_height="@dimen/bluetooth_pin_dialog_section_height"
             android:gravity="center_vertical"
             android:text="@string/bluetooth_enter_passkey_msg"
-            style="@style/TextAppearance.Car.Body2"
-            android:visibility="gone" />
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:visibility="gone"/>
 
         <RelativeLayout
             android:id="@+id/phonebook_sharing_message_confirm_pin_container"
-            android:layout_height="@dimen/car_double_line_list_item_height"
-            android:layout_width="match_parent" >
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bluetooth_pin_dialog_section_height">
             <CheckBox
                 android:id="@+id/phonebook_sharing_message_confirm_pin"
-                android:layout_width="@dimen/car_primary_icon_size"
-                android:layout_height="@dimen/car_primary_icon_size"
-                android:button="@drawable/ic_check_box"
+                android:layout_width="@dimen/icon_size"
+                android:layout_height="@dimen/icon_size"
                 android:layout_alignParentStart="true"
-                android:layout_centerVertical="true" />
+                android:layout_centerVertical="true"
+                android:button="@drawable/ic_check_box"/>
             <TextView
                 android:id="@+id/phonebook_sharing_message_confirm_pin_text"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_alignParentStart="true"
                 android:layout_centerVertical="true"
-                android:layout_marginStart="@dimen/car_keyline_3"
-                android:layout_marginEnd="@dimen/car_keyline_3"
-                style="@style/TextAppearance.Car.Body2.SingleLine"
-                android:gravity="center_vertical"/>
+                android:layout_marginEnd="@dimen/bluetooth_pin_dialog_text_margin_end"
+                android:layout_marginStart="@dimen/bluetooth_pin_dialog_text_margin_start"
+                android:ellipsize="end"
+                android:gravity="center_vertical"
+                android:maxLines="1"
+                android:textAppearance="?android:attr/textAppearanceMedium"/>
         </RelativeLayout>
 
     </LinearLayout>
diff --git a/res/layout/bluetooth_pin_entry.xml b/res/layout/bluetooth_pin_entry.xml
index 1e6bc03..343386f 100644
--- a/res/layout/bluetooth_pin_entry.xml
+++ b/res/layout/bluetooth_pin_entry.xml
@@ -1,107 +1,112 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 2017, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
 -->
+
 <ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="match_parent"
-    android:layout_width="match_parent">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
     <LinearLayout
-        android:layout_height="match_parent"
         android:layout_width="match_parent"
-        android:layout_marginStart="@dimen/car_keyline_1"
-        android:layout_marginEnd="@dimen/car_keyline_1"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="@dimen/bluetooth_pin_dialog_margin_end"
+        android:layout_marginStart="@dimen/bluetooth_pin_dialog_margin_start"
         android:orientation="vertical">
 
         <TextView
             android:id="@+id/message_below_pin"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/car_single_line_list_item_height"
+            android:layout_height="@dimen/bluetooth_pin_dialog_section_height"
             android:gravity="center_vertical"
-            style="@style/TextAppearance.Car.Body2"/>
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
 
         <FrameLayout
-            android:layout_height="@dimen/car_double_line_list_item_height"
-            android:layout_width="match_parent" >
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bluetooth_pin_dialog_section_height">
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:orientation="vertical"
-                android:layout_gravity="center_vertical">
+                android:layout_gravity="center_vertical"
+                android:orientation="vertical">
                 <EditText
                     android:id="@+id/text"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    style="@style/TextAppearance.Car.Body1"
                     android:inputType="textPassword"
-                    android:maxLines="1" />
+                    android:maxLines="1"
+                    android:textAppearance="?android:attr/textAppearanceLarge"/>
                 <TextView
                     android:id="@+id/pin_values_hint"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_marginTop="@dimen/car_padding_2"
+                    android:layout_marginTop="@dimen/bluetooth_pin_dialog_subtext_margin_top"
+                    android:ellipsize="end"
+                    android:maxLines="1"
                     android:text="@string/bluetooth_pin_values_hint"
-                    style="@style/TextAppearance.Car.Body2.SingleLine" />
+                    android:textAppearance="?android:attr/textAppearanceMedium"/>
             </LinearLayout>
         </FrameLayout>
 
         <RelativeLayout
-            android:layout_height="@dimen/car_double_line_list_item_height"
-            android:layout_width="match_parent" >
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bluetooth_pin_dialog_section_height">
             <CheckBox
                 android:id="@+id/alphanumeric_pin"
-                android:layout_width="@dimen/car_primary_icon_size"
-                android:layout_height="@dimen/car_primary_icon_size"
-                android:button="@drawable/ic_check_box"
+                android:layout_width="@dimen/icon_size"
+                android:layout_height="@dimen/icon_size"
                 android:layout_alignParentStart="true"
-                android:layout_centerVertical="true" />
+                android:layout_centerVertical="true"
+                android:button="@drawable/ic_check_box"/>
             <TextView
-                android:text="@string/bluetooth_enable_alphanumeric_pin"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_alignParentStart="true"
                 android:layout_centerVertical="true"
-                android:layout_marginStart="@dimen/car_keyline_3"
-                android:layout_marginEnd="@dimen/car_keyline_3"
-                style="@style/TextAppearance.Car.Body2.SingleLine"
-                android:gravity="center_vertical"/>
+                android:layout_marginEnd="@dimen/bluetooth_pin_dialog_text_margin_end"
+                android:layout_marginStart="@dimen/bluetooth_pin_dialog_text_margin_start"
+                android:ellipsize="end"
+                android:gravity="center_vertical"
+                android:maxLines="1"
+                android:text="@string/bluetooth_enable_alphanumeric_pin"
+                android:textAppearance="?android:attr/textAppearanceMedium"/>
         </RelativeLayout>
 
         <RelativeLayout
-            android:layout_height="@dimen/car_double_line_list_item_height"
-            android:layout_width="match_parent" >
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bluetooth_pin_dialog_section_height">
             <CheckBox
                 android:id="@+id/phonebook_sharing_message_entry_pin"
-                android:layout_width="@dimen/car_primary_icon_size"
-                android:layout_height="@dimen/car_primary_icon_size"
-                android:button="@drawable/ic_check_box"
+                android:layout_width="@dimen/icon_size"
+                android:layout_height="@dimen/icon_size"
                 android:layout_alignParentStart="true"
-                android:layout_centerVertical="true" />
+                android:layout_centerVertical="true"
+                android:button="@drawable/ic_check_box"/>
             <TextView
-                android:text="@string/bluetooth_pairing_shares_phonebook"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_alignParentStart="true"
                 android:layout_centerVertical="true"
-                android:layout_marginStart="@dimen/car_keyline_3"
-                android:layout_marginEnd="@dimen/car_keyline_3"
-                style="@style/TextAppearance.Car.Body2.SingleLine"
-                android:gravity="center_vertical"/>
+                android:layout_marginEnd="@dimen/bluetooth_pin_dialog_text_margin_end"
+                android:layout_marginStart="@dimen/bluetooth_pin_dialog_text_margin_start"
+                android:ellipsize="end"
+                android:gravity="center_vertical"
+                android:maxLines="1"
+                android:text="@string/bluetooth_pairing_shares_phonebook"
+                android:textAppearance="?android:attr/textAppearanceMedium"/>
         </RelativeLayout>
     </LinearLayout>
 
diff --git a/res/layout/brightness_tile.xml b/res/layout/brightness_tile.xml
index 80e2480..1cf7a5f 100644
--- a/res/layout/brightness_tile.xml
+++ b/res/layout/brightness_tile.xml
@@ -1,26 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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
-  -->
+    Copyright 2018 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.
+-->
 
 <SeekBar
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/seekbar"
-    android:layout_marginBottom="@dimen/car_padding_4"
-    android:progressDrawable="@drawable/ic_seekbar_track"
-    android:thumb="@drawable/ic_brightness_knob_stretched"
-    android:splitTrack="true"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/car_single_line_list_item_height"/>
+    android:layout_height="@dimen/brightness_seekbar_height"
+    android:layout_marginBottom="@dimen/brightness_seekbar_margin_bottom"
+    android:progressBackgroundTint="?attr/quickSettingsDisabledColor"
+    android:progressDrawable="@drawable/brightness_seekbar_track"
+    android:progressTint="?attr/quickSettingsEnabledColor"
+    android:splitTrack="true"
+    android:thumb="@drawable/ic_brightness_knob_stretched"/>
diff --git a/res/layout/car_setting_activity.xml b/res/layout/car_setting_activity.xml
new file mode 100644
index 0000000..127c62f
--- /dev/null
+++ b/res/layout/car_setting_activity.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <FrameLayout
+        android:id="@+id/action_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/action_bar_height"/>
+    <ProgressBar
+        android:id="@+id/progress_bar"
+        style="@style/TrimmedHorizontalProgressBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:indeterminate="true"
+        android:visibility="gone"/>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/divider_height"
+        android:background="?attr/dividerColor"/>
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <FrameLayout
+            android:id="@+id/fragment_container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+        <!-- set the text clickable to true so that it blocks touch event -->
+        <TextView
+            android:id="@+id/restricted_message"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="?android:attr/colorPrimary"
+            android:clickable="true"
+            android:gravity="center"
+            android:text="@string/restricted_while_driving"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:visibility="gone"/>
+    </FrameLayout>
+</LinearLayout>
diff --git a/res/layout/car_user_switcher.xml b/res/layout/car_user_switcher.xml
deleted file mode 100644
index 26c9329..0000000
--- a/res/layout/car_user_switcher.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:app="http://schemas.android.com/apk/res-auto"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@color/car_user_switcher_background_color"
-        android:orientation="vertical">
-
-
-    <com.android.car.settings.users.UserGridRecyclerView
-        android:id="@+id/user_grid"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginTop="@dimen/car_user_switcher_margin_top"
-        app:verticallyCenterListContent="true"
-        app:showPagedListViewDivider="false"
-        app:gutter="both"
-        app:itemSpacing="@dimen/car_user_switcher_vertical_spacing_between_users"/>
-
-</LinearLayout>
diff --git a/res/layout/car_user_switcher_pod.xml b/res/layout/car_user_switcher_pod.xml
deleted file mode 100644
index f7503d8..0000000
--- a/res/layout/car_user_switcher_pod.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-     Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:clipChildren="false"
-    android:alpha="0"
-    android:layout_height="wrap_content"
-    android:layout_width="match_parent"
-    android:orientation="vertical"
-    android:gravity="center">
-
-    <FrameLayout
-        android:id="@+id/current_user_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content">
-
-        <ImageView android:id="@+id/user_avatar"
-            android:layout_width="@dimen/car_user_switcher_image_avatar_size"
-            android:layout_height="@dimen/car_user_switcher_image_avatar_size"
-            android:background="@drawable/car_button_ripple_background_inverse"
-            android:layout_gravity="center"/>
-    </FrameLayout>
-
-    <TextView android:id="@+id/user_name"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/car_user_switcher_vertical_spacing_between_name_and_avatar"
-        android:textSize="@dimen/car_user_switcher_name_text_size"
-        android:textColor="@color/car_user_switcher_name_text_color"
-        android:ellipsize="end"
-        android:singleLine="true"
-        android:gravity="center"/>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/choose_lock_password.xml b/res/layout/choose_lock_password.xml
index 7767815..1dd716a 100644
--- a/res/layout/choose_lock_password.xml
+++ b/res/layout/choose_lock_password.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 The Android Open Source Project
 
-    Licensed under the Apache License, Version 2.0 (the "License")
+    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
+         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,
@@ -17,10 +17,9 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_marginHorizontal="@dimen/car_margin"
+    android:layout_marginHorizontal="@*android:dimen/action_bar_margin"
     android:orientation="horizontal">
 
     <FrameLayout
@@ -28,25 +27,18 @@
         android:layout_height="match_parent"
         android:layout_weight="@integer/content_weight">
 
-        <android.support.design.widget.TextInputLayout
+        <EditText
+            android:id="@+id/password_entry"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
-            android:paddingHorizontal="@dimen/car_padding_2"
-            app:hintTextAppearance="@style/TextAppearance.Car.Hint">
-
-            <android.support.design.widget.TextInputEditText
-                android:id="@+id/password_entry"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:focusableInTouchMode="true"
-                android:hint="@string/security_lock_password"
-                android:imeOptions="actionDone|flagNoExtractUi"
-                android:inputType="textPassword"
-                android:maxLines="1"
-                android:textAppearance="@style/TextAppearance.Car.Body2"/>
-
-        </android.support.design.widget.TextInputLayout>
+            android:focusableInTouchMode="true"
+            android:hint="@string/security_lock_password"
+            android:imeOptions="actionDone|flagNoExtractUi"
+            android:inputType="textPassword"
+            android:maxLines="1"
+            android:paddingHorizontal="@dimen/pin_password_entry_padding_horizontal"
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
 
     </FrameLayout>
 
@@ -58,33 +50,26 @@
         android:orientation="vertical">
 
         <ImageView
-            android:src="@drawable/ic_lock"
-            android:layout_height="@dimen/car_primary_icon_size"
-            android:layout_width="@dimen/car_primary_icon_size"/>
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:src="@drawable/ic_lock"/>
 
         <TextView
             android:id="@+id/title_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/choose_title_text_margin_bottom"
             android:gravity="center"
-            android:layout_marginBottom="@dimen/car_padding_2"
-            android:textAppearance="@style/TextAppearance.Car.Body1"
-            android:text="@string/lockscreen_choose_your_password"/>
+            android:text="@string/lockscreen_choose_your_password"
+            android:textAppearance="?android:attr/textAppearanceLarge"/>
 
         <TextView
             android:id="@+id/hint_text"
-            android:text="@string/choose_lock_password_hints"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Body2" />
+            android:text="@string/choose_lock_password_hints"
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
 
-        <Button
-            android:id="@+id/screen_lock_options"
-            style="@style/Widget.Car.Button.Borderless.Colored"
-            android:layout_width="wrap_content"
-            android:layout_marginTop="@dimen/car_padding_2"
-            android:text="@string/screen_lock_options"
-            android:visibility="gone"/>
     </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/res/layout/choose_lock_pattern.xml b/res/layout/choose_lock_pattern.xml
index 76d4d02..fc9bfbc 100644
--- a/res/layout/choose_lock_pattern.xml
+++ b/res/layout/choose_lock_pattern.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 The Android Open Source Project
 
-    Licensed under the Apache License, Version 2.0 (the "License")
+    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
+         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,
@@ -19,21 +19,21 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_marginHorizontal="@dimen/car_margin"
+    android:layout_marginHorizontal="@*android:dimen/action_bar_margin"
     android:orientation="horizontal"
-    android:paddingTop="@dimen/car_app_bar_height"
-    android:paddingBottom="@dimen/car_app_bar_height">
+    android:paddingBottom="@dimen/action_bar_height"
+    android:paddingTop="@dimen/action_bar_height">
 
     <!-- Start side: lock pattern -->
     <FrameLayout
         android:layout_width="0dp"
         android:layout_height="match_parent"
-        android:layout_weight="@integer/content_weight"
-        android:layout_gravity="center_vertical">
+        android:layout_gravity="center_vertical"
+        android:layout_weight="@integer/content_weight">
 
         <com.android.internal.widget.LockPatternView
-            style="@style/LockPatternStyle"
             android:id="@+id/lockPattern"
+            style="@style/LockPattern"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center"/>
@@ -48,32 +48,25 @@
         android:orientation="vertical">
 
         <ImageView
-            android:src="@drawable/ic_lock"
-            android:layout_height="@dimen/car_primary_icon_size"
-            android:layout_width="@dimen/car_primary_icon_size"/>
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:src="@drawable/ic_lock"/>
 
         <TextView
             android:id="@+id/title_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/choose_title_text_margin_bottom"
             android:gravity="center"
-            android:layout_marginBottom="@dimen/car_padding_2"
-            android:textAppearance="@style/TextAppearance.Car.Body1"
-            android:text="@string/lockscreen_choose_your_pattern"/>
+            android:text="@string/lockscreen_choose_your_pattern"
+            android:textAppearance="?android:attr/textAppearanceLarge"/>
 
         <TextView
             android:id="@+id/description_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Body2"/>
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
 
-        <Button
-            android:id="@+id/screen_lock_options"
-            style="@style/Widget.Car.Button.Borderless.Colored"
-            android:layout_width="wrap_content"
-            android:layout_marginTop="@dimen/car_padding_2"
-            android:text="@string/screen_lock_options"
-            android:visibility="gone"/>
     </LinearLayout>
 </LinearLayout>
diff --git a/res/layout/choose_lock_pin.xml b/res/layout/choose_lock_pin.xml
index 7980665..aa74736 100644
--- a/res/layout/choose_lock_pin.xml
+++ b/res/layout/choose_lock_pin.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 The Android Open Source Project
 
-    Licensed under the Apache License, Version 2.0 (the "License")
+    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
+         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,
@@ -19,14 +19,14 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_marginHorizontal="@dimen/car_margin"
+    android:layout_marginHorizontal="@*android:dimen/action_bar_margin"
     android:orientation="horizontal">
 
     <!-- Start side: lock pattern -->
     <FrameLayout
-        android:layout_weight="@integer/content_weight"
         android:layout_width="0dp"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent"
+        android:layout_weight="@integer/content_weight">
 
         <com.android.car.settings.security.PinPadView
             android:id="@+id/pin_pad"
@@ -38,54 +38,47 @@
 
     <!-- End side: pin entry field and messages -->
     <LinearLayout
-        android:layout_height="wrap_content"
         android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
         android:layout_weight="@integer/illustration_weight"
         android:gravity="center"
-        android:orientation="vertical"
-        android:layout_gravity="center_vertical">
+        android:orientation="vertical">
 
         <ImageView
-            android:src="@drawable/ic_lock"
-            android:layout_height="@dimen/car_primary_icon_size"
-            android:layout_width="@dimen/car_primary_icon_size"/>
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:src="@drawable/ic_lock"/>
 
         <TextView
             android:id="@+id/title_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/choose_pin_title_text_margin_bottom"
             android:gravity="center"
-            android:layout_marginBottom="@dimen/car_padding_5"
-            android:textAppearance="@style/TextAppearance.Car.Body1"
-            android:text="@string/lockscreen_choose_your_pin"/>
+            android:text="@string/lockscreen_choose_your_pin"
+            android:textAppearance="?android:attr/textAppearanceLarge"/>
 
         <EditText
             android:id="@+id/password_entry"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingHorizontal="@dimen/car_padding_2"
-            android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Body1"
-            android:maxLines="1"
-            android:inputType="textPassword"
             android:cursorVisible="false"
-            android:focusable="false"/>
+            android:focusable="false"
+            android:gravity="center"
+            android:inputType="textPassword"
+            android:maxLines="1"
+            android:paddingHorizontal="@dimen/pin_password_entry_padding_horizontal"
+            android:textAppearance="?android:attr/textAppearanceLarge"/>
 
         <!--  hint text -->
         <TextView
             android:id="@+id/hint_text"
-            android:text="@string/choose_lock_pin_hints"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Body2" />
+            android:text="@string/choose_lock_pin_hints"
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
 
-        <Button
-            android:id="@+id/screen_lock_options"
-            style="@style/Widget.Car.Button.Borderless.Colored"
-            android:layout_width="wrap_content"
-            android:layout_marginTop="@dimen/car_padding_2"
-            android:text="@string/screen_lock_options"
-            android:visibility="gone"/>
     </LinearLayout>
 </LinearLayout>
diff --git a/res/layout/confirm_lock_password.xml b/res/layout/confirm_lock_password.xml
new file mode 100644
index 0000000..437fa4d
--- /dev/null
+++ b/res/layout/confirm_lock_password.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <EditText
+        android:id="@+id/password_entry"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@*android:dimen/action_bar_margin"
+        android:gravity="center"
+        android:inputType="textPassword"
+        android:maxLines="1"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:text="@string/lock_settings_enter_password"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:freezesText="true"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceMedium"/>
+</LinearLayout>
diff --git a/res/layout/confirm_lock_password_fragment.xml b/res/layout/confirm_lock_password_fragment.xml
deleted file mode 100644
index 7895cad..0000000
--- a/res/layout/confirm_lock_password_fragment.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License")
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/container"
-    android:layout_height="match_parent"
-    android:layout_width="match_parent"
-    android:orientation="vertical"
-    android:gravity="center">
-
-    <EditText
-        android:id="@+id/password_entry"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginHorizontal="@dimen/car_margin"
-        android:gravity="center"
-        android:textAppearance="@style/TextAppearance.Car.Body1"
-        android:maxLines="1"
-        android:inputType="textPassword"/>
-
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:textAppearance="@style/TextAppearance.Car.Body1"
-        android:text="@string/lock_settings_enter_password"/>
-
-    <TextView
-        android:id="@+id/message"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:textAppearance="@style/TextAppearance.Car.Body2"
-        android:freezesText="true"/>
-</LinearLayout>
diff --git a/res/layout/confirm_lock_pattern.xml b/res/layout/confirm_lock_pattern.xml
new file mode 100644
index 0000000..eac1720
--- /dev/null
+++ b/res/layout/confirm_lock_pattern.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/confirm_lock_message_vertical_spacing"
+        android:text="@string/lock_settings_enter_pattern"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/confirm_lock_message_vertical_spacing"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+    <com.android.internal.widget.LockPatternView
+        android:id="@+id/lockPattern"
+        style="@style/LockPattern"
+        android:layout_width="@dimen/confirm_pattern_dimension"
+        android:layout_height="@dimen/confirm_pattern_dimension"/>
+</LinearLayout>
diff --git a/res/layout/confirm_lock_pattern_fragment.xml b/res/layout/confirm_lock_pattern_fragment.xml
deleted file mode 100644
index 01c45e3..0000000
--- a/res/layout/confirm_lock_pattern_fragment.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License")
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/container"
-    android:layout_height="match_parent"
-    android:layout_width="match_parent"
-    android:orientation="vertical"
-    android:gravity="center">
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/car_padding_2"
-        android:textAppearance="@style/TextAppearance.Car.Title2"
-        android:text="@string/lock_settings_enter_pattern"/>
-
-    <TextView
-        android:id="@+id/message"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/car_padding_2"
-        android:gravity="center"
-        android:textAppearance="@style/TextAppearance.Car.Body2"/>
-
-    <com.android.internal.widget.LockPatternView
-        style="@style/LockPatternStyle"
-        android:id="@+id/lockPattern"
-        android:layout_width="@dimen/confirm_pattern_dimension"
-        android:layout_height="@dimen/confirm_pattern_dimension"/>
-</LinearLayout>
diff --git a/res/layout/confirm_lock_pin.xml b/res/layout/confirm_lock_pin.xml
new file mode 100644
index 0000000..bb7cec4
--- /dev/null
+++ b/res/layout/confirm_lock_pin.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginHorizontal="@*android:dimen/action_bar_margin"
+    android:orientation="horizontal">
+
+    <!-- Start side: lock PIN -->
+    <FrameLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="@integer/content_weight"
+        android:gravity="center">
+
+        <com.android.car.settings.security.PinPadView
+            android:id="@+id/pin_pad"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:columnCount="3"/>
+    </FrameLayout>
+
+    <!-- End side: pin entry field and messages -->
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="@integer/illustration_weight"
+        android:orientation="vertical">
+
+        <EditText
+            android:id="@+id/password_entry"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/pin_password_entry_padding_horizontal"
+            android:cursorVisible="false"
+            android:focusable="false"
+            android:gravity="center"
+            android:inputType="textPassword"
+            android:maxLines="1"
+            android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/confirm_lock_message_vertical_spacing"
+            android:gravity="center"
+            android:text="@string/lock_settings_enter_pin"
+            android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/confirm_lock_pin_fragment.xml b/res/layout/confirm_lock_pin_fragment.xml
deleted file mode 100644
index accb52a..0000000
--- a/res/layout/confirm_lock_pin_fragment.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License")
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="horizontal">
-
-    <!-- Start side: lock PIN -->
-    <FrameLayout
-        android:layout_weight="1"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:gravity="center">
-
-        <com.android.car.settings.security.PinPadView
-            android:id="@+id/pin_pad"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:columnCount="3"/>
-    </FrameLayout>
-
-    <!-- End side: pin entry field and messages -->
-    <LinearLayout
-        android:layout_height="wrap_content"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:layout_gravity="center_vertical">
-
-        <EditText
-            android:id="@+id/password_entry"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginHorizontal="@dimen/car_padding_5"
-            android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Body1"
-            android:maxLines="1"
-            android:inputType="textPassword"
-            android:cursorVisible="false"
-            android:focusable="false"/>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:layout_marginBottom="@dimen/car_padding_2"
-            android:textAppearance="@style/TextAppearance.Car.Body1"
-            android:text="@string/lock_settings_enter_pin"/>
-
-        <TextView
-            android:id="@+id/message"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Body2" />
-    </LinearLayout>
-</LinearLayout>
diff --git a/res/layout/data_usage_summary_preference.xml b/res/layout/data_usage_summary_preference.xml
new file mode 100644
index 0000000..b3c7c4b
--- /dev/null
+++ b/res/layout/data_usage_summary_preference.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:orientation="vertical"
+    android:paddingBottom="@dimen/data_usage_summary_preference_padding_bottom"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/usage_indicator_preference_title_margin_bottom"
+        android:layout_marginTop="@dimen/usage_indicator_preference_title_margin_top"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+    <ProgressBar
+        android:id="@android:id/progress"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/usage_indicator_preference_progressbar_height"
+        android:progressDrawable="@drawable/color_progress_bar"/>
+
+    <LinearLayout
+        android:id="@+id/progress_bar_labels"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/usage_indicator_preference_label_margin_bottom"
+        android:layout_marginTop="@dimen/usage_indicator_preference_label_margin_top"
+        android:orientation="horizontal">
+        <TextView android:id="@android:id/text1"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"/>
+
+        <TextView android:id="@android:id/text2"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAppearance="?android:attr/textAppearanceSmall"/>
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/data_limit_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"/>
+    <TextView
+        android:id="@+id/remaining_billing_cycle_time_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"/>
+    <TextView
+        android:id="@+id/carrier_info_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"/>
+    <Button
+        android:id="@+id/manage_subscription_button"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/data_usage_summary_preference_button_height"
+        android:layout_marginTop="@dimen/data_usage_summary_preference_button_margin_top"
+        android:minWidth="@dimen/data_usage_summary_preference_button_min_width"
+        android:text="@string/launch_manage_plan_text"/>
+</LinearLayout>
diff --git a/res/layout/date_picker.xml b/res/layout/date_picker.xml
index ebcc35c..23c37f2 100644
--- a/res/layout/date_picker.xml
+++ b/res/layout/date_picker.xml
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 
 <DatePicker
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/date_picker"
-    android:scaleX="1.6"
-    android:scaleY="1.6"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:layout_gravity="center"
     android:datePickerMode="spinner"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content" />
+    android:scaleX="1.6"
+    android:scaleY="1.6"/>
diff --git a/res/layout/details_preference_widget.xml b/res/layout/details_preference_widget.xml
new file mode 100644
index 0000000..4f9fc41
--- /dev/null
+++ b/res/layout/details_preference_widget.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:src="@drawable/ic_settings_gear"/>
diff --git a/res/layout/dropdown_preference_widget.xml b/res/layout/dropdown_preference_widget.xml
new file mode 100644
index 0000000..f6eb1a2
--- /dev/null
+++ b/res/layout/dropdown_preference_widget.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:src="@drawable/ic_arrow_drop_down"/>
diff --git a/res/layout/edit_icon_preference_widget.xml b/res/layout/edit_icon_preference_widget.xml
new file mode 100644
index 0000000..3797630
--- /dev/null
+++ b/res/layout/edit_icon_preference_widget.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:src="@drawable/ic_edit"/>
diff --git a/res/layout/edit_username_fragment.xml b/res/layout/edit_username_fragment.xml
index 8893111..2980e6a 100644
--- a/res/layout/edit_username_fragment.xml
+++ b/res/layout/edit_username_fragment.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 Google Inc.
+    Copyright 2018 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
+         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,
@@ -17,30 +17,20 @@
 
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingStart="@dimen/car_margin"
-    android:paddingEnd="@dimen/car_margin">
+    android:paddingEnd="@dimen/edit_username_padding_end"
+    android:paddingStart="@dimen/edit_username_padding_start"
+    android:paddingTop="@dimen/edit_username_text_padding_top">
 
-    <android.support.design.widget.TextInputLayout
-        android:id="@+id/user_name"
+    <EditText
+        android:id="@+id/user_name_text_edit"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:focusableInTouchMode="true"
         android:hint="@string/user_name_label"
-        android:paddingEnd="@dimen/car_keyline_1"
-        android:paddingStart="@dimen/car_keyline_1"
-        android:paddingTop="@dimen/car_padding_3"
-        app:hintTextAppearance="@style/TextAppearance.Car.Hint">
-
-        <android.support.design.widget.TextInputEditText
-            android:id="@+id/user_name_text_edit"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:focusableInTouchMode="true"
-            android:imeOptions="actionDone|flagNoExtractUi"
-            android:inputType="text"
-            android:maxLines="1"
-            android:textAppearance="@style/TextAppearance.Car.Body1"/>
-    </android.support.design.widget.TextInputLayout>
+        android:imeOptions="actionDone|flagNoExtractUi"
+        android:inputType="text"
+        android:maxLines="1"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
 </FrameLayout>
diff --git a/res/layout/fallback_home_finishing_boot.xml b/res/layout/fallback_home_finishing_boot.xml
new file mode 100644
index 0000000..9331ea0
--- /dev/null
+++ b/res/layout/fallback_home_finishing_boot.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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="match_parent"
+    android:background="#80000000"
+    android:forceHasOverlappingRendering="false">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@*android:string/android_start_title"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="20sp"/>
+
+        <ProgressBar
+            style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12.75dp"
+            android:colorControlActivated="?android:attr/textColorPrimary"
+            android:indeterminate="true"/>
+
+    </LinearLayout>
+</FrameLayout>
diff --git a/res/layout/homepage.xml b/res/layout/homepage.xml
deleted file mode 100644
index cbda622..0000000
--- a/res/layout/homepage.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<android.support.v7.widget.RecyclerView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/recycler_view"
-        android:scrollbars="vertical"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/res/layout/icon_widget_line_item.xml b/res/layout/icon_widget_line_item.xml
deleted file mode 100644
index 6b2888f..0000000
--- a/res/layout/icon_widget_line_item.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/LineItem" >
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_marginStart="@dimen/car_keyline_1"
-        android:layout_alignParentStart="true"
-        style="@style/ListIcon"/>
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:layout_alignParentStart="true"
-        android:layout_centerVertical="true"
-        android:layout_marginStart="@dimen/car_keyline_3"
-        android:layout_marginEnd="@dimen/car_keyline_3" >
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/TextAppearance.Car.Body1.SingleLine" />
-        <TextView
-            android:id="@+id/desc"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/TextAppearance.Car.Body2.SingleLine"
-            android:visibility="gone" />
-    </LinearLayout>
-    <ImageButton
-        android:id="@+id/action"
-        style="@style/ListIcon"
-        android:layout_marginEnd="@dimen/car_keyline_1"
-        android:layout_alignParentEnd="true"
-        android:layout_centerVertical="true"
-        android:src="@drawable/ic_settings_gear"
-        android:background="@null"
-        android:visibility="gone" />
-    <View
-        android:id="@+id/line_item_divider"
-        android:layout_alignParentBottom="true"
-        android:background="@color/car_list_divider"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/car_list_divider_height" />
-</RelativeLayout>
diff --git a/res/layout/list_fragment.xml b/res/layout/list_fragment.xml
deleted file mode 100644
index 4b78932..0000000
--- a/res/layout/list_fragment.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2017 Google Inc.
-
-    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.
--->
-
-<androidx.car.widget.PagedListView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/list"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    app:showPagedListViewDivider="true"
-    app:alignDividerStartTo="@id/container"
-    style="@style/SettingList" />
-
diff --git a/res/layout/logical_preference_group.xml b/res/layout/logical_preference_group.xml
new file mode 100644
index 0000000..6845054
--- /dev/null
+++ b/res/layout/logical_preference_group.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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="wrap_content"
+    android:layout_height="wrap_content"/>
diff --git a/res/layout/make_admin_preference_widget.xml b/res/layout/make_admin_preference_widget.xml
new file mode 100644
index 0000000..981b235
--- /dev/null
+++ b/res/layout/make_admin_preference_widget.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:maxEms="@integer/button_max_ems"
+    android:text="@string/grant_admin_permissions_button_text"
+    android:textAppearance="@style/PreferenceButtonTextAppearance"/>
diff --git a/res/layout/master_switch_widget.xml b/res/layout/master_switch_widget.xml
new file mode 100644
index 0000000..1b6bfff
--- /dev/null
+++ b/res/layout/master_switch_widget.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<Switch
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/master_switch"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:clickable="false"
+    android:focusable="false"/>
diff --git a/res/layout/pin_pad_view.xml b/res/layout/pin_pad_view.xml
index 5952ac4..ffe6162 100644
--- a/res/layout/pin_pad_view.xml
+++ b/res/layout/pin_pad_view.xml
@@ -1,17 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!--
+    Copyright 2018 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
+    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
+         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.
+    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.
 -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
@@ -19,69 +20,103 @@
     <Button
         android:id="@+id/key1"
         style="@style/PinPadKey"
-        android:text="@string/one"
-        android:tag="1"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="1"
+        android:text="@string/one"/>
     <Button
         android:id="@+id/key2"
         style="@style/PinPadKey"
-        android:text="@string/two"
-        android:tag="2"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="2"
+        android:text="@string/two"/>
     <Button
         android:id="@+id/key3"
         style="@style/PinPadKey"
-        android:text="@string/three"
-        android:tag="3"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="3"
+        android:text="@string/three"/>
 
     <!-- Row 2 -->
     <Button
         android:id="@+id/key4"
         style="@style/PinPadKey"
-        android:text="@string/four"
-        android:tag="4"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="4"
+        android:text="@string/four"/>
     <Button
         android:id="@+id/key5"
         style="@style/PinPadKey"
-        android:text="@string/five"
-        android:tag="5"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="5"
+        android:text="@string/five"/>
     <Button
         android:id="@+id/key6"
         style="@style/PinPadKey"
-        android:text="@string/six"
-        android:tag="6"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="6"
+        android:text="@string/six"/>
 
     <!-- Row 3 -->
     <Button
         android:id="@+id/key7"
         style="@style/PinPadKey"
-        android:text="@string/seven"
-        android:tag="7"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="7"
+        android:text="@string/seven"/>
     <Button
         android:id="@+id/key8"
         style="@style/PinPadKey"
-        android:text="@string/eight"
-        android:tag="8"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="8"
+        android:text="@string/eight"/>
     <Button
         android:id="@+id/key9"
         style="@style/PinPadKey"
-        android:text="@string/nine"
-        android:tag="9"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="9"
+        android:text="@string/nine"/>
 
     <!-- Row 4 -->
     <ImageButton
         android:id="@+id/key_backspace"
         style="@style/PinPadKey"
-        android:src="@drawable/ic_backspace"
-        android:tint="@color/car_body3"
-        android:contentDescription="@string/backspace_key"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:contentDescription="@string/backspace_key"
+        android:src="@drawable/ic_backspace"/>
     <Button
         android:id="@+id/key0"
         style="@style/PinPadKey"
-        android:text="@string/zero"
-        android:tag="0"/>
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="0"
+        android:text="@string/zero"/>
     <ImageButton
         android:id="@+id/key_enter"
         style="@style/PinPadKey"
-        android:src="@drawable/ic_done"
-        android:tint="@color/car_body3"
-        android:contentDescription="@string/enter_key"/>
-</merge>
\ No newline at end of file
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:contentDescription="@string/enter_key"
+        android:src="@drawable/ic_check"/>
+</merge>
diff --git a/res/layout/preference_dialog_edittext.xml b/res/layout/preference_dialog_edittext.xml
new file mode 100644
index 0000000..6fcd61f
--- /dev/null
+++ b/res/layout/preference_dialog_edittext.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginBottom="@dimen/alert_dialog_margin_bottom"
+    android:layout_marginTop="@dimen/alert_dialog_margin_top"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@android:id/message"
+        style="?android:attr/textAppearanceSmall"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/alert_dialog_title_margin_bottom"
+        android:layout_marginEnd="@dimen/alert_dialog_title_margin_end"
+        android:layout_marginStart="@dimen/alert_dialog_title_margin_start"
+        android:textColor="?android:attr/textColorSecondary"
+        android:visibility="gone"/>
+
+    <EditText
+        android:id="@android:id/edit"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/alert_dialog_edit_text_margin_end"
+        android:layout_marginStart="@dimen/alert_dialog_edit_text_margin_start"/>
+
+</LinearLayout>
diff --git a/res/layout/preference_dialog_password_edittext.xml b/res/layout/preference_dialog_password_edittext.xml
new file mode 100644
index 0000000..bcfd422
--- /dev/null
+++ b/res/layout/preference_dialog_password_edittext.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginBottom="@dimen/alert_dialog_margin_bottom"
+    android:layout_marginTop="@dimen/alert_dialog_margin_top"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@android:id/message"
+        style="?android:attr/textAppearanceSmall"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/alert_dialog_title_margin_bottom"
+        android:layout_marginEnd="@dimen/alert_dialog_title_margin_end"
+        android:layout_marginStart="@dimen/alert_dialog_title_margin_start"
+        android:textColor="?android:attr/textColorSecondary"
+        android:visibility="gone"/>
+
+    <EditText
+        android:id="@android:id/edit"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/alert_dialog_edit_text_margin_end"
+        android:layout_marginStart="@dimen/alert_dialog_edit_text_margin_start"/>
+
+    <CheckBox
+        android:id="@+id/checkbox"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/alert_dialog_password_checkbox_margin_end"
+        android:layout_marginStart="@dimen/alert_dialog_password_checkbox_margin_start"
+        android:layout_marginTop="@dimen/alert_dialog_password_checkbox_margin_top"
+        android:text="@string/show_password"/>
+
+</LinearLayout>
diff --git a/res/layout/progress_bar_preference.xml b/res/layout/progress_bar_preference.xml
new file mode 100644
index 0000000..c224c01
--- /dev/null
+++ b/res/layout/progress_bar_preference.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:background="?android:attr/selectableItemBackground"
+              android:gravity="center_vertical"
+              android:minHeight="?android:attr/listPreferredItemHeightSmall"
+              android:orientation="horizontal"
+              android:paddingBottom="@dimen/progress_bar_preference_padding_bottom"
+              android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+              android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+              android:paddingTop="@dimen/progress_bar_preference_padding_top">
+    <androidx.preference.internal.PreferenceImageView
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/icon_size"
+        android:layout_height="@dimen/icon_size"
+        android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:id="@android:id/title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:singleLine="true"
+                android:textAlignment="viewStart"
+                android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"/>
+
+            <TextView
+                android:id="@android:id/summary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:maxLines="1"
+                android:textAlignment="viewEnd"
+                android:textAppearance="?android:attr/textAppearanceSmall"/>
+        </LinearLayout>
+
+        <ProgressBar
+            android:id="@android:id/progress"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/progress_bar_preference_progressbar_margin_top"/>
+
+        <LinearLayout
+            android:id="@+id/progress_bar_labels"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/progress_bar_preference_label_margin_top"
+            android:orientation="horizontal">
+            <TextView android:id="@android:id/text1"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"/>
+
+            <TextView android:id="@android:id/text2"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textAppearance="?android:attr/textAppearanceSmall"/>
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/quick_settings.xml b/res/layout/quick_settings.xml
index e6d14c1..0087a97 100644
--- a/res/layout/quick_settings.xml
+++ b/res/layout/quick_settings.xml
@@ -1,25 +1,39 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
 
-<androidx.car.widget.PagedListView
+    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.
+-->
+
+<RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/list"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:showPagedListViewDivider="false"
-    style="@style/SettingList" />
\ No newline at end of file
+    android:layout_height="match_parent"
+    android:layout_marginHorizontal="@*android:dimen/action_bar_margin">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_above="@+id/build_info"/>
+
+    <TextView
+        android:id="@+id/build_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:visibility="gone"/>
+
+</RelativeLayout>
diff --git a/res/layout/regulatory_info.xml b/res/layout/regulatory_info.xml
new file mode 100644
index 0000000..b4526c5
--- /dev/null
+++ b/res/layout/regulatory_info.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+  <ImageView
+      android:id="@+id/regulatory_info"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:adjustViewBounds="true"
+      android:contentDescription="@string/regulatory_labels"
+      android:scaleType="centerCrop"
+      android:src="@drawable/regulatory_info"/>
+</ScrollView>
diff --git a/res/layout/seekbar_preference.xml b/res/layout/seekbar_preference.xml
new file mode 100644
index 0000000..247e84e
--- /dev/null
+++ b/res/layout/seekbar_preference.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:orientation="horizontal"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+    <androidx.preference.internal.PreferenceImageView
+        android:id="@android:id/icon"
+        android:layout_width="@*android:dimen/car_primary_icon_size"
+        android:layout_height="@*android:dimen/car_primary_icon_size"
+        android:layout_marginBottom="@*android:dimen/car_padding_2"
+        android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:layout_marginTop="@*android:dimen/car_padding_2"/>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@*android:dimen/car_padding_2"
+        android:layout_marginTop="@*android:dimen/car_padding_2"
+        android:layout_weight="1">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/title"
+            android:layout_below="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"/>
+
+        <!-- Using UnPressableLinearLayout as a workaround to disable the pressed state propagation
+        to the children of this container layout. Otherwise, the animated pressed state will also
+        play for the thumb in the AbsSeekBar in addition to the preference's ripple background.
+        The background of the SeekBar is also set to null to disable the ripple background -->
+        <androidx.preference.UnPressableLinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/title"
+            android:layout_below="@android:id/summary"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+            <SeekBar
+                android:id="@+id/seekbar"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@null"
+                android:clickable="false"
+                android:focusable="false"/>
+
+            <TextView
+                android:id="@+id/seekbar_value"
+                android:layout_width="@*android:dimen/car_keyline_3"
+                android:layout_height="wrap_content"
+                android:fadingEdge="horizontal"
+                android:gravity="end|center_vertical"
+                android:scrollbars="none"
+                android:textAppearance="?android:attr/textAppearanceListItemSecondary"/>
+        </androidx.preference.UnPressableLinearLayout>
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/res/layout/settings_fragment.xml b/res/layout/settings_fragment.xml
new file mode 100644
index 0000000..3567112
--- /dev/null
+++ b/res/layout/settings_fragment.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginHorizontal="@*android:dimen/action_bar_margin"
+    tools:ignore="NewApi">
+
+    <FrameLayout
+        android:id="@android:id/list_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+    <TextView
+        android:id="@android:id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:visibility="gone"/>
+
+</FrameLayout>
diff --git a/res/layout/spinner.xml b/res/layout/spinner.xml
deleted file mode 100644
index 1178f82..0000000
--- a/res/layout/spinner.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:id="@+id/spinnerTarget"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:textAppearance="@style/TextAppearance.Car.Body1.SingleLine"
-/>
diff --git a/res/layout/spinner_drop_down.xml b/res/layout/spinner_drop_down.xml
deleted file mode 100644
index 4f401a8..0000000
--- a/res/layout/spinner_drop_down.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:id="@+id/spinnerTarget"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:paddingTop="@dimen/tile_top_bottom_padding"
-          android:paddingBottom="@dimen/tile_top_bottom_padding"
-          android:paddingStart="@dimen/car_keyline_1"
-          android:textAppearance="@style/TextAppearance.Car.Body1.SingleLine"
-/>
diff --git a/res/layout/suggestion_preference.xml b/res/layout/suggestion_preference.xml
new file mode 100644
index 0000000..ad549d8
--- /dev/null
+++ b/res/layout/suggestion_preference.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginBottom="@dimen/suggestions_top_bottom_margin"
+    android:layout_marginTop="@dimen/suggestions_top_bottom_margin"
+    android:background="@android:color/transparent">
+    <androidx.cardview.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardCornerRadius="@dimen/suggestions_corner_radius">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/suggestionsPrimaryColor"
+                android:paddingBottom="@dimen/suggestions_text_padding_bottom"
+                android:paddingEnd="@dimen/suggestions_padding_end"
+                android:paddingStart="@dimen/suggestions_padding_start"
+                android:paddingTop="@dimen/suggestions_text_padding_top">
+                <TextView
+                    android:id="@android:id/title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    android:textAppearance="?android:attr/textAppearanceLarge"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"/>
+                <TextView
+                    android:id="@android:id/summary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/suggestions_subtext_margin_top"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@android:id/title"/>
+                <ImageView
+                    android:id="@android:id/icon"
+                    android:layout_width="@dimen/icon_size"
+                    android:layout_height="@dimen/icon_size"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"/>
+            </androidx.constraintlayout.widget.ConstraintLayout>
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/suggestions_action_bar_height"
+                android:background="?attr/suggestionsSecondaryColor"
+                android:paddingEnd="@dimen/suggestions_padding_end"
+                android:paddingStart="@dimen/suggestions_padding_start">
+                <Button
+                    android:id="@+id/dismiss_button"
+                    style="?android:attr/borderlessButtonStyle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:text="@string/suggestion_dismiss_button"
+                    android:textColor="?android:attr/textColorPrimary"/>
+            </FrameLayout>
+        </LinearLayout>
+    </androidx.cardview.widget.CardView>
+</FrameLayout>
diff --git a/res/layout/summary_preference_widget.xml b/res/layout/summary_preference_widget.xml
new file mode 100644
index 0000000..a69094c
--- /dev/null
+++ b/res/layout/summary_preference_widget.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/widget_summary"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical|end"
+    android:textAlignment="viewEnd"
+    android:textAppearance="?android:attr/textAppearanceSmall"
+    android:textColor="?android:attr/textColorSecondary"/>
diff --git a/res/layout/suw_action_bar_with_button.xml b/res/layout/suw_action_bar_with_button.xml
deleted file mode 100644
index fc5ba27..0000000
--- a/res/layout/suw_action_bar_with_button.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/car_app_bar_height">
-
-    <FrameLayout
-        android:id="@+id/action_bar_icon_container"
-        android:layout_width="@dimen/car_margin"
-        android:layout_height="match_parent"
-        android:layout_alignParentStart="true">
-
-        <ImageView
-            android:id="@+id/back_button"
-            android:background="@drawable/suw_button_ripple_bg"
-            style="@style/ListIcon.ActionBar"/>
-    </FrameLayout>
-
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_marginEnd="@dimen/car_keyline_1"
-        android:layout_toEndOf="@id/action_bar_icon_container"
-        android:gravity="center_vertical"
-        android:textAppearance="@style/TextAppearance.Car.Title2"/>
-
-    <Button
-        android:id="@+id/action_button2"
-        style="?android:attr/borderlessButtonStyle"
-        android:visibility="gone"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/car_button_height"
-        android:layout_toStartOf="@id/action_button1"
-        android:layout_centerVertical="true"
-        android:ellipsize="end"
-        android:maxLines="1"/>
-
-    <Button
-        android:id="@+id/action_button1"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/car_button_height"
-        android:layout_alignParentEnd="true"
-        android:layout_centerVertical="true"
-        android:layout_marginStart="@dimen/car_padding_4"
-        android:ellipsize="end"
-        android:maxLines="1"/>
-
-    <ProgressBar
-        android:id="@+id/progress_bar"
-        style="@style/Widget.Car.ProgressBar.Horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:visibility="gone"
-        android:indeterminate="true"/>
-</RelativeLayout>
diff --git a/res/layout/suw_activity.xml b/res/layout/suw_activity.xml
deleted file mode 100644
index 50d7490..0000000
--- a/res/layout/suw_activity.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-    <android.support.v7.widget.Toolbar
-        android:id="@+id/toolbar"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/car_app_bar_height"
-        app:contentInsetStart="0dp"
-        style="@style/ActionBarStyle.Car" />
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:id="@+id/fragment_container"/>
-</LinearLayout>
diff --git a/res/layout/tile.xml b/res/layout/tile.xml
index b24c34f..28d71da 100644
--- a/res/layout/tile.xml
+++ b/res/layout/tile.xml
@@ -1,36 +1,36 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2018 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:padding="@dimen/toggle_padding"
-    android:layout_marginStart="@dimen/car_padding_2"
-    android:layout_marginEnd="@dimen/car_padding_2"
-    android:layout_marginBottom="@dimen/car_padding_5"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:layout_marginBottom="@dimen/tile_margin_bottom"
+    android:layout_marginEnd="@dimen/tile_margin_end"
+    android:layout_marginStart="@dimen/tile_margin_start"
     android:gravity="center_horizontal"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:padding="@dimen/tile_padding">
     <FrameLayout
         android:id="@+id/icon_container"
-        android:layout_width="@dimen/car_touch_target_size"
-        android:layout_height="@dimen/car_touch_target_size"
+        android:layout_width="@dimen/touch_target_size"
+        android:layout_height="@dimen/touch_target_size"
+        android:layout_marginBottom="@dimen/tile_icon_margin_bottom"
         android:background="@drawable/circle_ripple_bg"
-        android:layout_marginBottom="@dimen/car_padding_4"
         android:gravity="center">
         <View
             android:id="@+id/icon_background"
@@ -39,31 +39,15 @@
             android:background="@drawable/circle_bg"/>
         <ImageView
             android:id="@+id/tile_icon"
-            android:layout_width="@dimen/car_primary_icon_size"
-            android:layout_height="@dimen/car_primary_icon_size"
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
             android:layout_gravity="center"
+            android:scaleType="fitCenter"
             android:src="@drawable/ic_settings_wifi"
-            android:tint="@color/toggle_icon_tint"
-            style="@style/ListIcon" />
+            android:tint="@color/toggle_icon_tint"/>
     </FrameLayout>
-    <LinearLayout
-        android:id="@+id/text_container"
-        android:layout_height="wrap_content"
+    <TextView
+        android:id="@+id/tile_text"
         android:layout_width="wrap_content"
-        android:orientation="horizontal">
-        <TextView
-            android:id="@+id/tile_text"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Label1"/>
-        <ImageView
-            android:id="@+id/deep_dive_icon"
-            android:layout_width="@dimen/car_primary_icon_size"
-            android:layout_height="@dimen/car_primary_icon_size"
-            android:src="@drawable/ic_arrow_drop_down"
-            android:tint="@color/car_label1"
-            android:layout_marginStart="@dimen/car_padding_1"
-            android:visibility="gone"/>
-    </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+        android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/res/layout/time_picker.xml b/res/layout/time_picker.xml
index 9666a9f..404467c 100644
--- a/res/layout/time_picker.xml
+++ b/res/layout/time_picker.xml
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 
 <TimePicker
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/time_picker"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
     android:scaleX="1.6"
     android:scaleY="1.6"
-    android:layout_gravity="center"
-    android:timePickerMode="spinner"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"/>
+    android:timePickerMode="spinner"/>
diff --git a/res/layout/two_action_preference.xml b/res/layout/two_action_preference.xml
new file mode 100644
index 0000000..e629bfc
--- /dev/null
+++ b/res/layout/two_action_preference.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@android:color/transparent"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall">
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/selectableItemBackground"
+        android:clipToPadding="false"
+        android:gravity="start|center_vertical"
+        android:paddingBottom="@dimen/preference_padding_bottom"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingTop="@dimen/preference_padding_top">
+        <androidx.preference.internal.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"/>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:orientation="vertical">
+            <TextView
+                android:id="@android:id/title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:singleLine="true"
+                android:textAppearance="?android:attr/textAppearanceLarge"/>
+            <TextView
+                android:id="@android:id/summary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"/>
+        </LinearLayout>
+    </LinearLayout>
+    <LinearLayout
+        android:id="@+id/action_widget_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent">
+        <View
+            android:layout_width="@dimen/two_action_preference_divider_width"
+            android:layout_height="match_parent"
+            android:layout_marginBottom="@dimen/preference_padding_bottom"
+            android:layout_marginTop="@dimen/preference_padding_top"
+            android:background="?attr/dividerColor"/>
+        <!-- Preference should place its actual preference widget here. -->
+        <FrameLayout
+            android:id="@android:id/widget_frame"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:background="?android:attr/selectableItemBackground"
+            android:minWidth="?android:attr/listPreferredItemHeightSmall"
+            android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+            android:paddingStart="?android:attr/listPreferredItemPaddingStart"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/usage_bytes_threshold_picker.xml b/res/layout/usage_bytes_threshold_picker.xml
new file mode 100644
index 0000000..a8cf70f
--- /dev/null
+++ b/res/layout/usage_bytes_threshold_picker.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent">
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_begin="@dimen/usage_pickers_margin_vertical"/>
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline_start"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@dimen/usage_bytes_picker_margin_horizontal"/>
+
+    <FrameLayout
+        android:id="@+id/bytes_threshold_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/usage_pickers_text_margin_end"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        app:layout_constraintBottom_toTopOf="@+id/guideline_bottom"
+        app:layout_constraintEnd_toStartOf="@+id/bytes_units"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="@+id/guideline_start"
+        app:layout_constraintTop_toBottomOf="@+id/guideline_top">
+        <EditText
+            android:id="@+id/bytes_threshold"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:ems="@integer/data_usage_bytes_threshold_ems"
+            android:inputType="numberDecimal"
+            android:minWidth="@dimen/usage_bytes_picker_edit_text_min_width"
+            android:textAppearance="?android:attr/textAppearanceLarge"/>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/up_arrow_container"
+        android:layout_width="@dimen/touch_target_size"
+        android:layout_height="@dimen/touch_target_size"
+        android:background="?android:attr/selectableItemBackground"
+        app:layout_constraintEnd_toEndOf="@+id/bytes_units"
+        app:layout_constraintStart_toStartOf="@+id/bytes_units"
+        app:layout_constraintTop_toBottomOf="@+id/guideline_top">
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_keyboard_arrow_up"/>
+    </FrameLayout>
+
+    <NumberPicker
+        android:id="@+id/bytes_units"
+        android:layout_width="@dimen/touch_target_size"
+        android:layout_height="match_parent"
+        app:layout_constraintBottom_toTopOf="@+id/down_arrow_container"
+        app:layout_constraintEnd_toEndOf="@+id/guideline_end"
+        app:layout_constraintStart_toEndOf="@+id/bytes_threshold_container"
+        app:layout_constraintTop_toBottomOf="@+id/up_arrow_container"/>
+
+    <FrameLayout
+        android:id="@+id/down_arrow_container"
+        android:layout_width="@dimen/touch_target_size"
+        android:layout_height="@dimen/touch_target_size"
+        android:background="?android:attr/selectableItemBackground"
+        app:layout_constraintBottom_toTopOf="@+id/guideline_bottom"
+        app:layout_constraintEnd_toEndOf="@+id/bytes_units"
+        app:layout_constraintStart_toStartOf="@+id/bytes_units">
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_keyboard_arrow_down"/>
+    </FrameLayout>
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline_end"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="@dimen/usage_bytes_picker_margin_horizontal"/>
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline_bottom"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_end="@dimen/usage_pickers_margin_vertical"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/usage_cycle_reset_day_of_month_picker.xml b/res/layout/usage_cycle_reset_day_of_month_picker.xml
new file mode 100644
index 0000000..93d18e0
--- /dev/null
+++ b/res/layout/usage_cycle_reset_day_of_month_picker.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent">
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_begin="@dimen/usage_pickers_margin_vertical"/>
+
+    <TextView
+        android:id="@+id/subtitle_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/usage_pickers_text_margin_end"
+        android:text="@string/cycle_reset_day_of_month_picker_subtitle"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        app:layout_constraintBottom_toTopOf="@+id/guideline_bottom"
+        app:layout_constraintEnd_toStartOf="@+id/cycle_reset_day_of_month"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/guideline_top"/>
+
+    <FrameLayout
+        android:id="@+id/up_arrow_container"
+        android:layout_width="@dimen/touch_target_size"
+        android:layout_height="@dimen/touch_target_size"
+        android:background="?android:attr/selectableItemBackground"
+        app:layout_constraintEnd_toEndOf="@+id/cycle_reset_day_of_month"
+        app:layout_constraintStart_toStartOf="@+id/cycle_reset_day_of_month"
+        app:layout_constraintTop_toBottomOf="@+id/guideline_top">
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_keyboard_arrow_up"/>
+    </FrameLayout>
+
+    <NumberPicker
+        android:id="@+id/cycle_reset_day_of_month"
+        android:layout_width="@dimen/touch_target_size"
+        android:layout_height="match_parent"
+        app:layout_constraintBottom_toTopOf="@+id/down_arrow_container"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/subtitle_text"
+        app:layout_constraintTop_toBottomOf="@+id/up_arrow_container"/>
+
+    <FrameLayout
+        android:id="@+id/down_arrow_container"
+        android:layout_width="@dimen/touch_target_size"
+        android:layout_height="@dimen/touch_target_size"
+        android:background="?android:attr/selectableItemBackground"
+        app:layout_constraintBottom_toTopOf="@+id/guideline_bottom"
+        app:layout_constraintEnd_toEndOf="@+id/cycle_reset_day_of_month"
+        app:layout_constraintStart_toStartOf="@+id/cycle_reset_day_of_month">
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_keyboard_arrow_down"/>
+    </FrameLayout>
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline_bottom"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_end="@dimen/usage_pickers_margin_vertical"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/usage_indicator_preference.xml b/res/layout/usage_indicator_preference.xml
new file mode 100644
index 0000000..b6ad180
--- /dev/null
+++ b/res/layout/usage_indicator_preference.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:orientation="vertical"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/usage_indicator_preference_title_margin_top"
+        android:layout_marginBottom="@dimen/usage_indicator_preference_title_margin_bottom"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+    <ProgressBar
+        android:id="@android:id/progress"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:minHeight="@dimen/usage_indicator_preference_progressbar_height"
+        android:layout_height="wrap_content"
+        android:progressDrawable="@drawable/color_progress_bar"/>
+
+    <LinearLayout
+        android:id="@+id/progress_bar_labels"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/usage_indicator_preference_label_margin_top"
+        android:layout_marginBottom="@dimen/usage_indicator_preference_label_margin_bottom"
+        android:orientation="horizontal">
+        <TextView android:id="@android:id/text1"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"/>
+
+        <TextView android:id="@android:id/text2"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAppearance="?android:attr/textAppearanceSmall"/>
+    </LinearLayout>
+
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/usage_indicator_preference_summary_margin_bottom"
+        android:textAppearance="?android:attr/textAppearanceSmall"/>
+</LinearLayout>
diff --git a/res/layout/user_details_fragment.xml b/res/layout/user_details_fragment.xml
deleted file mode 100644
index b8a60bd..0000000
--- a/res/layout/user_details_fragment.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 Google Inc.
-
-    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.
--->
-
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:paddingStart="@dimen/car_margin"
-    android:paddingEnd="@dimen/car_margin">
-    <android.support.design.widget.TextInputLayout
-        android:id="@+id/user_name"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:hint="@string/user_name_label"
-        android:paddingEnd="@dimen/car_keyline_1"
-        android:paddingStart="@dimen/car_keyline_1"
-        android:paddingTop="@dimen/car_padding_3"
-        app:hintTextAppearance="@style/TextAppearance.Car.Hint">
-        <android.support.design.widget.TextInputEditText
-            android:id="@+id/user_name_text_edit"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:focusableInTouchMode="true"
-            android:imeOptions="actionDone|flagNoExtractUi"
-            android:inputType="text"
-            android:maxLines="1"
-            android:textAppearance="@style/TextAppearance.Car.Body1"/>
-    </android.support.design.widget.TextInputLayout>
-
-    <Button
-        android:id="@+id/ok_button"
-        android:visibility="gone"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/car_button_height"
-        android:layout_gravity="center_vertical"
-        android:text="@android:string/ok"
-        android:maxLines="1"
-        android:ellipsize="end"
-        style="@style/Widget.Car.Button.Borderless.Colored"
-        android:minWidth="@dimen/car_button_min_width"
-        android:textColor="@color/action_bar_btn"
-        android:layout_toStartOf="@id/cancel_button"
-        android:layout_below="@id/user_name"/>
-
-    <Button
-        android:id="@+id/cancel_button"
-        android:visibility="gone"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/car_button_height"
-        android:layout_gravity="center_vertical"
-        android:text="@string/cancel"
-        android:maxLines="1"
-        android:ellipsize="end"
-        style="@style/Widget.Car.Button.Borderless.Colored"
-        android:minWidth="@dimen/car_button_min_width"
-        android:textColor="@color/action_bar_btn"
-        android:layout_alignEnd="@id/user_name"
-        android:layout_below="@id/user_name"/>
-</RelativeLayout>
diff --git a/res/layout/user_switcher.xml b/res/layout/user_switcher.xml
new file mode 100644
index 0000000..ad2d456
--- /dev/null
+++ b/res/layout/user_switcher.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.android.car.settings.users.UserGridRecyclerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/user_grid"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:background="?attr/userSwitcherBackground"/>
diff --git a/res/layout/user_switcher_pod.xml b/res/layout/user_switcher_pod.xml
new file mode 100644
index 0000000..086d8fb
--- /dev/null
+++ b/res/layout/user_switcher_pod.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:alpha="0"
+    android:clipChildren="false"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <FrameLayout
+        android:id="@+id/current_user_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <ImageView
+            android:id="@+id/user_avatar"
+            android:layout_width="@dimen/user_switcher_image_avatar_size"
+            android:layout_height="@dimen/user_switcher_image_avatar_size"
+            android:layout_gravity="center"
+            android:background="?android:attr/selectableItemBackgroundBorderless"/>
+    </FrameLayout>
+
+    <TextView
+        android:id="@+id/user_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/user_switcher_vertical_spacing_between_name_and_avatar"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:singleLine="true"
+        android:textAppearance="?attr/userSwitcherNameTextAppearance"/>
+
+</LinearLayout>
diff --git a/res/layout/wifi_list.xml b/res/layout/wifi_list.xml
deleted file mode 100644
index 1bb82df..0000000
--- a/res/layout/wifi_list.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical" >
-    <ProgressBar
-        android:id="@+id/wifi_search_progress"
-        android:visibility="gone"
-        style="@style/TrimmedHorizontalProgressBar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:indeterminate="true" />
-    <ViewSwitcher
-        android:id="@+id/view_switcher"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <androidx.car.widget.PagedListView
-            android:id="@+id/list"
-            style="@style/SettingList"
-            app:showPagedListViewDivider="false"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-        <TextView
-            android:id="@+id/message"
-            android:layout_gravity="center"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/TextAppearance.Car.Body1"/>
-    </ViewSwitcher>
-</LinearLayout>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 6ec3a98..bcc8907 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Saambindversoek"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tik om met <xliff:g id="DEVICE_NAME">%1$s</xliff:g> saam te bind."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Tale"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Klank"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Luivolume"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigasievolume"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Vee gebruiker uit"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nuwe gebruiker"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gas"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Admin"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Aangemeld as admin"</string>
     <string name="user_switch" msgid="6544839750534690781">"Wissel"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Jy (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Naam"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Herprobeer"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Slaan oor"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Stel \'n skermslot"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Kies jou PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Kies jou patroon"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Kies jou wagwoord"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Huidige skermslot"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Stel \'n patroon vir sekuriteit"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Vee uit"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Vee uit"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Kanselleer"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Bevestig"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Moet minstens 4 karakters lank wees"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Wagwoord is 4-8 karakters, met 1 syfer"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Moet minstens <xliff:g id="COUNT">%d</xliff:g> karakters lank wees"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN moet minstens <xliff:g id="COUNT">%d</xliff:g> syfers hê"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Moet korter as <xliff:g id="NUMBER">%d</xliff:g> karakters wees"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Toesteladministrateur laat nie toe dat \'n onlangse PIN gebruik word nie"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Jou IT-administrateur blokkeer algemene PIN\'e. Probeer \'n ander PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Dit mag nie \'n ongeldige karakter insluit nie."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Wagwoord is ongeldig; moet minstens 4 karakters lank wees."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Wagwoord is ongeldig; moet 4-8 karakters hê, moet minstens 1 syfer en 1 letter bevat, geen witspasie nie."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Moet minstens <xliff:g id="COUNT">%d</xliff:g> letters bevat</item>
       <item quantity="one">Moet minstens een letter bevat</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"voltooi opstelling"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"nie nou nie"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Kenmerk is nie beskikbaar terwyl jy bestuur nie."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Kan nie gebruiker byvoeg terwyl jy bestuur nie."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index f856f43..9129cb9 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"የማጣመር ጥየቃ"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"ከ«<xliff:g id="DEVICE_NAME">%1$s</xliff:g>» ጋር ለማጣመር ነካ ያድርጉ።"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ቋንቋዎች"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ድምጽ"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"የጥሪ የድምጽ መጠን"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"የአሰሳ ድምጽ መጠን"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ተጠቃሚን ሰርዝ"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"አዲስ ተጠቃሚ"</string>
     <string name="user_guest" msgid="3465399481257448601">"እንግዳ"</string>
-    <string name="user_admin" msgid="1535484812908584809">"አስተዳዳሪ"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"እንደ አስተዳዳሪ በመለያ ይግቡ"</string>
     <string name="user_switch" msgid="6544839750534690781">"ቀይር"</string>
     <string name="current_user_name" msgid="3813671533249316823">"እርስዎ (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"ስም"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"እንደገና ይሞክሩ"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"ዝለል"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"የማያ ገጽ መቆለፊያን ያቀናብሩ"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"የእርስዎን ፒን ይምረጡ"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"የእርስዎን ሥርዓተ ጥለት ይምረጡ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"የይለፍ ቃልዎን ይምረጡ"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"አሁን ያለ ማያ ገጽ መቆለፊያ"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"ለደህንነት ሲባል፣ ሥርዓተ ጥለትን ያቀናብሩ"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"አጽዳ"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"አጽዳ"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"ይቅር"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"አረጋግጥ"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"ቢያንስ 4 ቁምፊዎች መሆን አለበት"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"የይለፍ ቃል 4-8 አኃዞች እና ቢያንስ 1 ቁጥር ያለው መሆን አለበት"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"ቢያንስ <xliff:g id="COUNT">%d</xliff:g> ቁምፊዎች መሆን አለበት"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"ፒን ቢያንስ <xliff:g id="COUNT">%d</xliff:g> አኃዞች መሆን አለበት"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"የይለፍ ቃል ከ<xliff:g id="NUMBER">%d</xliff:g> ቁምፊዎች ያነሰ መሆን አለበት"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"የመሣሪያ አስተዳዳሪው የቅርብ ጊዜ ፒንን መጠቀም አይፈቅድም"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"የተለመዱ ፒኖች በአይቲ አስተዳዳሪዎ የታገዱ ናቸው። የተለየ ፒን ይሞክሩ።"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ይህ ልክ ያልሆነ ቁምፊን ማካተት አይችልም።"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"የይለፍ ቃል ልክ ያልሆነ ነው፣ ቢያንስ 4 ቁምፊዎች መሆን አለበት"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"የይለፍ ቃል ልክ ያልሆነ ነው፣ 4-8 ቁምፊዎች የሆነ፣ ቢያንስ 1 አኃዝ እና 1 ፊደል የያዘ እና ምንም ባዶ ክፍተት የሌለው መሆን አለበት።"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">ቢያንስ <xliff:g id="COUNT">%d</xliff:g> ፊደሎችን መያዝ አለበት</item>
       <item quantity="other">ቢያንስ <xliff:g id="COUNT">%d</xliff:g> ፊደሎችን መያዝ አለበት</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ማዋቀር ጨርስ"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"አሁን አልፈልግም"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"እየነዱ ሳለ ባህሪ አይገኝም።"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"እየነዱ ሳለ ተጠቃሚን ማከል አይቻልም።"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index fe813aa..c979032 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"طلب الإقران"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"انقر للإقران مع <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"اللغات"</string>
     <string name="sound_settings" msgid="3072423952331872246">"صوت"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"مستوى صوت الرنين"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"مستوى صوت التنقل"</string>
@@ -174,8 +173,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"حذف حساب مستخدم"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"مستخدم جديد"</string>
     <string name="user_guest" msgid="3465399481257448601">"الضيف"</string>
-    <string name="user_admin" msgid="1535484812908584809">"المشرف"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"تمّ تسجيل الدخول كمشرف."</string>
     <string name="user_switch" msgid="6544839750534690781">"تبديل"</string>
     <string name="current_user_name" msgid="3813671533249316823">"أنت (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"الاسم"</string>
@@ -218,9 +215,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"إعادة المحاولة"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"تخطّي"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"تعيين قفل شاشة"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"اختيار رقم التعريف الشخصي"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"اختيار النّقش"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"اختيار كلمة المرور"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"قفل الشاشة الحالي"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"للحفاظ على الأمان، يجب تعيين نقش."</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"محو"</string>
@@ -253,7 +248,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"محو"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"إلغاء"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"تأكيد"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"يجب أن تتكوَّن كلمة المرور من 4 أحرف على الأقل."</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"يجب أن تتكون كلمة المرور من 4 إلى 8 أحرف مع رقم واحد على الأقل"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"يجب أن تحتوي على <xliff:g id="COUNT">%d</xliff:g> حرف على الأقل."</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"يجب أن يحتوي PIN على <xliff:g id="COUNT">%d</xliff:g> رقم على الأقل."</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"يجب ألا تزيد كلمة المرور عن <xliff:g id="NUMBER">%d</xliff:g> حرف."</string>
@@ -262,7 +257,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"لا يسمح مشرف الجهاز باستخدام رقم تعريف شخصي تم استخدامه مؤخرًا."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"حظرَ مشرف تكنولوجيا المعلومات استخدام أرقام التعريف الشخصية الشائعة. جرِّب استخدام رقم تعريف شخصي مختلف."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"يجب ألا تتضمّن كلمة المرور حرفًا غير صالح."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"كلمة المرور غير صالحة، ويجب أن تتكوَّن من 4 أحرف على الأقل."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"كلمة المرور غير صالحة لأنها يجب أن تتكّون من 4 إلى 8 أحرف، وأن تحتوي على رقم واحد على الأقل، مع عدم استخدام مسافة بيضاء."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="zero">يجب أن تحتوي كلمة المرور على <xliff:g id="COUNT">%d</xliff:g> حرف على الأقل.</item>
       <item quantity="two">يجب أن تحتوي كلمة المرور على حرفين (<xliff:g id="COUNT">%d</xliff:g>) على الأقل.</item>
@@ -329,5 +324,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"الانتهاء من الإعداد"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ليس الآن"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"لا تتوفَّر الميزة أثناء القيادة."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"لا يمكن إضافة مستخدم أثناء القيادة."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 9a9edaf..fc62f29 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -20,7 +20,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="settings_label" msgid="5147911978211079839">"ছেটিংবোৰ"</string>
     <string name="more_settings_label" msgid="3867559443480110616">"অধিক"</string>
-    <string name="display_settings" msgid="5325515247739279185">"ডিছপ্লে’"</string>
+    <string name="display_settings" msgid="5325515247739279185">"ডিছপ্লে"</string>
     <string name="brightness" msgid="2919605130898772866">"উজ্জ্বলতাৰ স্তৰ"</string>
     <string name="auto_brightness_title" msgid="9124647862844666581">"অভিযোজিত উজ্জ্বলতা"</string>
     <string name="auto_brightness_summary" msgid="4741887033140384352">"উপলব্ধ পোহৰ অনুযায়ী উজ্জ্বলতাৰ পৰিমাণ অপ্টিমাইজ কৰক"</string>
@@ -37,7 +37,7 @@
     <string name="wifi_failed_forget_message" msgid="121732682699377206">"নেটৱৰ্ক পাহৰিব পৰা নগ\'ল"</string>
     <string name="wifi_failed_connect_message" msgid="4447498225022147324">"নেটৱৰ্কৰ সৈতে সংযোগ কৰিব পৰা নগ\'ল"</string>
     <string name="wifi_setup_add_network" msgid="3660498520389954620">"নেটৱৰ্ক যোগ কৰক"</string>
-    <string name="wifi_disabled" msgid="5013262438128749950">"ৱাই-ফাই অক্ষম কৰা হ’ল"</string>
+    <string name="wifi_disabled" msgid="5013262438128749950">"ৱাই-ফাই অক্ষম কৰা হ\'ল"</string>
     <string name="wifi_setup_connect" msgid="3512399573397979101">"সংযোগ কৰক"</string>
     <string name="wifi_password" msgid="5565632142720292397">"পাছৱৰ্ড"</string>
     <string name="wifi_show_password" msgid="8423293211933521097">"পাছৱৰ্ড দেখুৱাওক"</string>
@@ -58,7 +58,7 @@
     <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"ব্লুটুথ"</string>
     <string name="bluetooth_quick_toggle_summary" msgid="4390699431893305353">"ব্লুটুথ অন কৰক"</string>
     <string name="bluetooth_settings" msgid="3878243366013638982">"ব্লুটুথ"</string>
-    <string name="bluetooth_disabled" msgid="4187409401590350572">"ব্লুটুথ অক্ষম কৰা হ’ল"</string>
+    <string name="bluetooth_disabled" msgid="4187409401590350572">"ব্লুটুথ অক্ষম কৰা হ\'ল"</string>
     <string name="bluetooth_settings_title" msgid="3794688574569688649">"ব্লুটুথ"</string>
     <string name="bluetooth_settings_summary" msgid="4023303473646769835">"সংযোগসমূহ পৰিচালনা কৰক, ডিভাইচৰ নাম আৰু তাক বিচাৰি পাব পৰা অৱস্থা ছেট কৰক"</string>
     <string name="bluetooth_talkback_computer" msgid="5223330129934365312">"কম্পিউটাৰ"</string>
@@ -81,7 +81,7 @@
     <string name="bluetooth" msgid="5235115159234688629">"ব্লুটুথ"</string>
     <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"ব্লুটুথ যোৰ লগোৱা ক’ড"</string>
     <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"পিনত বৰ্ণ বা প্ৰতীকবোৰ থাকে"</string>
-    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"যোৰা লগোৱা ক’ডটো টাইপ কৰক আৰু তাৰ পিছত ৰিটাৰ্ণ বা এণ্টাৰ কী হেঁচক"</string>
+    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"যোৰা লগোৱা ক’ডটো টাইপ কৰক আৰু তাৰপিছত ৰিটাৰ্ণ বা এণ্টাৰ কী হেঁচক"</string>
     <string name="bluetooth_pairing_request" msgid="4769675459526556801">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ৰ সৈতে যোৰা লগাবনে?"</string>
     <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ক আপোনাৰ সম্পৰ্কসূচী আৰু কলৰ ইতিহাস চাবলৈ দিয়ক"</string>
     <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"আপুনি এই পিনটো আনটো ডিভাইচত টাইপ কৰিবলগীয়াও হ’ব পাৰে৷"</string>
@@ -91,9 +91,8 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"যোৰা লগোৱাৰ অনুৰোধ"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ৰ লগত যোৰা লগাবলৈ টিপক"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ভাষা"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ধ্বনি"</string>
-    <string name="ring_volume_title" msgid="3135241004980719442">"ৰিঙৰ ভলিউম"</string>
+    <string name="ring_volume_title" msgid="3135241004980719442">"ৰিংৰ ভলিউম"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"নেভিগেশ্বনৰ ভলিউম"</string>
     <string name="incoming_call_volume_title" msgid="6972117872424656876">"ৰিংট’ন"</string>
     <string name="notification_volume_title" msgid="6749411263197157876">"জাননী"</string>
@@ -116,7 +115,7 @@
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>টা অতিৰিক্ত অনুমতি</item>
     </plurals>
     <string name="system_setting_title" msgid="6864599341809463440">"ছিষ্টেম"</string>
-    <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"ছিষ্টেম আপডে’ট"</string>
+    <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"ছিষ্টেম আপডেট"</string>
     <string name="system_update_settings_list_item_summary" msgid="7395202602021608371"></string>
     <string name="firmware_version" msgid="8491753744549309333">"Android সংস্কৰণ"</string>
     <string name="security_patch" msgid="4794276590178386903">"Android সুৰক্ষা পেচ্চ স্তৰ"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ব্যৱহাৰকাৰীক মচক"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"নতুন ব্যৱহাৰকাৰী"</string>
     <string name="user_guest" msgid="3465399481257448601">"অতিথি"</string>
-    <string name="user_admin" msgid="1535484812908584809">"প্ৰশাসক"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"প্ৰশাসক হিচাপে ছাইন ইন হৈ আছে"</string>
     <string name="user_switch" msgid="6544839750534690781">"ছুইচ্চ"</string>
     <string name="current_user_name" msgid="3813671533249316823">"আপুনি (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"নাম"</string>
@@ -198,41 +195,45 @@
     <string name="remove_user_error_retry" msgid="8291692909396995093">"পুনৰ চেষ্টা কৰক"</string>
     <string name="user_add_user_title" msgid="7458813670614932479">"নতুন ব্যৱহাৰকাৰী যোগ কৰেনে?"</string>
     <string name="user_add_user_message_setup" msgid="6030901156040053106">"আপুনি যেতিয়া কোনো ব্য়ৱহাৰকাৰীক যোগ কৰে সেই ব্য়ক্তিজনে নিজৰ বাবে খালী ঠাই ছেট আপ কৰা প্ৰয়োজন।"</string>
-    <string name="user_add_user_message_update" msgid="1528170913388932459">"যিকোনো ব্য়ৱহাৰকাৰীয়ে সকলো ব্য়ৱহাৰকাৰীৰ বাবে এপসমূহ আপডে’ট কৰিব পাৰে।"</string>
+    <string name="user_add_user_message_update" msgid="1528170913388932459">"যিকোনো ব্য়ৱহাৰকাৰীয়ে সকলো ব্য়ৱহাৰকাৰীৰ বাবে এপসমূহ আপডেট কৰিব পাৰে।"</string>
     <string name="security_settings_title" msgid="6955331714774709746">"সুৰক্ষা"</string>
     <string name="security_settings_subtitle" msgid="2244635550239273229">"স্ক্ৰীণ লক"</string>
-    <string name="security_lock_none" msgid="1054645093754839638">"নাই"</string>
+    <!-- no translation found for security_lock_none (1054645093754839638) -->
+    <skip />
     <string name="security_lock_pattern" msgid="1174352995619563104">"আৰ্হি"</string>
-    <string name="security_lock_pin" msgid="4891899974369503200">"পিন"</string>
+    <!-- no translation found for security_lock_pin (4891899974369503200) -->
+    <skip />
     <string name="security_lock_password" msgid="4420203740048322494">"পাছৱৰ্ড"</string>
     <string name="lock_settings_picker_title" msgid="6590330165050361632">"কোনো লকৰ প্ৰকাৰ বাছক"</string>
-    <string name="screen_lock_options" msgid="7023338635352915768">"স্ক্ৰীণ লক সম্পৰ্কীয় বিকল্প"</string>
-    <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"আপোনাৰ আৰ্হি দিয়ক"</string>
+    <string name="screen_lock_options" msgid="7023338635352915768">"স্ক্ৰীণ লকৰ বিকল্পসমূহ"</string>
+    <!-- no translation found for lock_settings_enter_pattern (4826034565853171624) -->
+    <skip />
     <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"নিশ্চিত কৰক"</string>
     <string name="lockpattern_restart_button_text" msgid="9355771277617537">"আকৌ আঁকক"</string>
-    <string name="continue_button_text" msgid="5129979170426836641">"অব্যাহত ৰাখক"</string>
+    <string name="continue_button_text" msgid="5129979170426836641">"অব্য়াহত ৰাখক"</string>
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"পুনৰ চেষ্টা কৰক"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"এৰি যাওক"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"স্ক্ৰীণ ছেট কৰক"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"আপোনাৰ পিন বাছনি কৰক"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"আপোনাৰ আৰ্হি বাছক"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"আপোনাৰ পাছৱর্ড বাছনি কৰক"</string>
-    <string name="current_screen_lock" msgid="637651611145979587">"বৰ্তমান ব্যৱহাৰ হৈ থকা স্ক্ৰীণ লক"</string>
+    <!-- no translation found for current_screen_lock (637651611145979587) -->
+    <skip />
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"সুৰক্ষাৰ বাবে কোনো আৰ্হি ছেট কৰক"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"মচক"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"বাতিল কৰক"</string>
     <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"আপোনাৰ নতুন আনলক আৰ্হি"</string>
     <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"লক খোলাৰ আৰ্হি আঁকক"</string>
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"আৰ্হি অঁকা কাৰ্য হ\'লে আঙুলিটো উঠাওক"</string>
-    <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"আৰ্হি ৰেক\'ৰ্ড কৰা হ’ল"</string>
+    <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"আৰ্হি ৰেক\'ৰ্ড কৰা হ\'ল"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"নিশ্চিত কৰিবলৈ আৰ্হিটো আকৌ আঁকক"</string>
     <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"কমেও ৪টা বিন্দু লগ লগাই আকৌ চেষ্টা কৰক।"</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"ভুল আৰ্হি"</string>
     <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"লক খোলাৰ আৰ্হি অঁকা উপায়"</string>
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"আৰ্হি ছেভ কৰোঁতে আঁসোৱাহ"</string>
     <string name="okay" msgid="4589873324439764349">"ঠিক আছে"</string>
-    <string name="remove_screen_lock_title" msgid="1234382338764193387">"স্ক্ৰীণ লক আঁতৰাবনে?"</string>
-    <string name="remove_screen_lock_message" msgid="6675850371585564965">"এই কাৰ্যই সকলোকে আপোনাৰ একাউণ্টৰ এক্সেছ দিব"</string>
+    <!-- no translation found for remove_screen_lock_title (1234382338764193387) -->
+    <skip />
+    <!-- no translation found for remove_screen_lock_message (6675850371585564965) -->
+    <skip />
     <string name="lock_settings_enter_pin" msgid="1669172111244633904">"আপোনাৰ পিন দিয়ক"</string>
     <string name="lock_settings_enter_password" msgid="2636669926649496367">"আপোনাৰ পাছৱৰ্ড দিয়ক"</string>
     <string name="choose_lock_pin_message" msgid="2963792070267774417">"সুৰক্ষাৰ বাবে কোনো পিন ছেট কৰক"</string>
@@ -244,12 +245,12 @@
     <string name="lockscreen_wrong_pin" msgid="4922465731473805306">"ভুল পিন"</string>
     <string name="lockscreen_wrong_password" msgid="5757087577162231825">"ভুল পাছৱৰ্ড"</string>
     <string name="choose_lock_password_message" msgid="6124341145027370784">"সুৰক্ষাৰ বাবে কোনো পাছৱৰ্ড ছেট কৰক"</string>
-    <string name="confirm_your_password_header" msgid="7052891840366724938">"আপোনাৰ পাছৱৰ্ডটো আকৌ দিয়ক"</string>
+    <string name="confirm_your_password_header" msgid="7052891840366724938">"আপোনাৰ পাছৱৰ্ড আকৌ দিয়ক"</string>
     <string name="confirm_passwords_dont_match" msgid="7300229965206501753">"পাছৱৰ্ড মিলা নাই"</string>
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"মচক"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"বাতিল কৰক"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"নিশ্চিত কৰক"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"অতি কমেও ৪টা বর্ণ থাকিব লাগিব"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"পাছৱৰ্ডত কমেও ১টা সংখ্য়াসহ ৪-৮ লৈকে বৰ্ণ থাকিবই লাগিব"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"অতি কমেও <xliff:g id="COUNT">%d</xliff:g>টা বৰ্ণ থাকিব লাগিব"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"পিনটোত অতি কমেও <xliff:g id="COUNT">%d</xliff:g>টা অংক থাকিব লাগিব"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g>তকৈ কম সংখ্যক বৰ্ণ থাকিব লাগিব"</string>
@@ -258,7 +259,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ডিভাইচৰ প্ৰশাসকে শেহতীয়া পিন ব্যৱহাৰ কৰিব নিদিয়ে"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"আপোনাৰ আইটি প্ৰশাসকে উমৈহতীয়া পিনবোৰ অৱৰোধ কৰি থৈছে৷ বেলেগ পিন ব্য়ৱহাৰ কৰি চাওক৷"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ইয়াত অমান্য় বৰ্ণ থাকিব নোৱাৰে।"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"অমান্য পাছৱর্ড, অতি কমেও ৪টা বর্ণ থাকিব লাগিব।"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"অমান্য় পাছৱৰ্ড, ইয়াত ৪-৮ বৰ্ণ থাকিবই লাগিব য\'ত কমেও ১টা সংখ্য়া, ১টা আখৰ হওক কিন্তু মাজত কোনো খালী ঠাই হ\'ব নোৱাৰিব।"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">অতি কমেও <xliff:g id="COUNT">%d</xliff:g> টা বৰ্ণ থাকিব লাগিব</item>
       <item quantity="other">অতি কমেও <xliff:g id="COUNT">%d</xliff:g> বৰ্ণ থাকিব লাগিব</item>
@@ -287,7 +288,7 @@
     <string name="error_saving_password" msgid="8334882262622500658">"পাছৱৰ্ড ছেভ কৰোঁতে আঁসোৱাহ"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"আইটি প্ৰশাসকে উমৈহতীয়া পাছৱৰ্ডসমূহ অৱৰোধ কৰি থৈছে। বেলেগ পাছৱৰ্ড ব্যৱাহৰ কৰি চাওক।"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="38813552228809240">"সংখ্যাবোৰৰ ঊৰ্ধ্বগামী, অধোগামী বা পুনৰাবৃত্তি ক্ৰমক অনুমতি দিয়া নহয়।"</string>
-    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"স্ক্ৰীণ লক সম্পৰ্কীয় বিকল্প"</string>
+    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"স্ক্ৰীণ লকৰ বিকল্পসমূহ"</string>
     <string name="forget" msgid="3971143908183848527">"পাহৰক"</string>
     <string name="delete_button" msgid="5840500432614610850">"মচক"</string>
     <string name="remove_button" msgid="6664656962868194178">"আঁতৰাওক"</string>
@@ -300,6 +301,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ডেম\'ৰ পৰা বাহিৰ হওক"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ছেট আপ সমাপ্ত কৰক"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"এতিয়াই নহয়"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ড্ৰাইভিং কৰা সময়ত এই সুবিধাটো নাথাকে।"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ড্ৰাইভিং কৰিথকা সময়ত ব্য়ৱহাৰকাৰী যোগ কৰিব নোৱাৰি।"</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 62628bb..12d6598 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Birləşdirmə sorğusu"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ilə birləşdirmək üçün toxunun"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Dillər"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Səs"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Zəngin səs səviyyəsi"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Naviqasiyanın səs səviyyəsi"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"İstifadəçinin silinməsi"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Yeni istifadəçi"</string>
     <string name="user_guest" msgid="3465399481257448601">"Qonaq"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Admin"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"İnzibatçı kimi daxil olunub"</string>
     <string name="user_switch" msgid="6544839750534690781">"Dəyişin"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Siz (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Ad"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Yenidən cəhd edin"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Keçin"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ekran kilidi təyin edin"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PİN seçin"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Modelinizi seçin"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Parolunuzu seçin"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Cari ekran kilidi"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Təhlükəsizlik üçün model təyin edin"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Silin"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Silin"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Ləğv edin"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Təsdiq edin"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Ən azı 4 simvol olmalıdır"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Parol ən azı 1-i rəqəm olmaqla 4-8 simvoldan ibarət olmalıdır"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Ən az <xliff:g id="COUNT">%d</xliff:g> simvol olmalıdır"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN kod ən azı <xliff:g id="COUNT">%d</xliff:g> rəqəm olmalıdır"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> simvoldan az olmalıdır"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Cihaz admini son vaxtlardakı PIN kodu istifadə etməyə icazə vermir"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Ümumi PIN kodlar IT admini tərəfindən blok edilib. Fərqli PIN kod sınayın."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Yalnış simvol daxil edə bilməzsiniz"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Parol yanlışdır, ən azı 4 simvol olmalıdır."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Parol yanlışdır, ən azı 1-i hərf olmaqla, boşluq qoyulmadan 4-8 arası simvoldan ibarət olmalıdır."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Ən azı <xliff:g id="COUNT">%d</xliff:g> hərf olmalıdır</item>
       <item quantity="one">Ən azı 1 hərf olmalıdır</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"quraşdırmanı bitirin"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"indi yox"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Xüsusiyyət avtomobil idarə edərkən əlçatan deyil."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Avtomobil idarə edərkən istifadəçi əlavə etmək mümkün deyil."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 362d859..4ae5e72 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Zahtev za uparivanje"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Dodirnite da biste uparili sa uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Jezici"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Zvuk"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Jačina zvuka zvona"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Jačina zvuka za navigaciju"</string>
@@ -108,7 +107,7 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Nijedna dozvola nije odobrena"</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Nijedna dozvola nije zahtevana"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Potrošnja podataka"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Potrošnja podataka apl."</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Potrošnja podataka aplik."</string>
     <string name="force_stop" msgid="2153183697014720520">"Prinudno zaustavi"</string>
     <string name="computing_size" msgid="5791407621793083965">"Izračunava se..."</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
@@ -171,8 +170,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Izbrišite korisnika"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Novi korisnik"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gost"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Prijavljeni ste kao administrator"</string>
     <string name="user_switch" msgid="6544839750534690781">"Prebacite na drugog korisnika"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Vi (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Ime"</string>
@@ -215,9 +212,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Probaj ponovo"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Preskoči"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Podesite zaključavanje ekrana"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Odaberite PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Odaberite šablon"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Odaberite lozinku"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Trenutno zaključavanje ekrana"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Podesite šablon iz bezbednosnih razloga"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Obriši"</string>
@@ -250,7 +245,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Obriši"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Otkaži"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Potvrdi"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Mora da sadrži najmanje 4 znaka"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Lozinka mora da ima 4–8 znakova sa najmanje 1 brojem"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Minimalan broj znakova je <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Minimalan broj cifara za PIN je <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Maksimalan broj znakova je <xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -259,7 +254,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Administrator uređaja ne dozvoljava upotrebu nedavno korišćenog PIN-a"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"IT administrator blokira česte PIN-ove. Izaberite drugi PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Ne sme da obuhvata nevažeći znak."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Lozinka je nevažeća, mora da sadrži najmanje 4 znaka."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Lozinka je nevažeća, mora da sadrži 4–8 znakova, najmanje 1 cifru, 1 slovo, bez razmaka."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Mora da sadrži najmanje <xliff:g id="COUNT">%d</xliff:g> slovo</item>
       <item quantity="few">Mora da sadrži najmanje <xliff:g id="COUNT">%d</xliff:g> slova</item>
@@ -302,11 +297,12 @@
     <string name="backspace_key" msgid="1545590866688979099">"Taster za brisanje unazad"</string>
     <string name="enter_key" msgid="2121394305541579468">"Taster Enter"</string>
     <string name="exit_retail_button_text" msgid="6093240315583384473">"Napustite režim demonstr."</string>
-    <string name="exit_retail_mode_dialog_title" msgid="7970631760237469168">"Da napustite režim demonstracije"</string>
+    <string name="exit_retail_mode_dialog_title" msgid="7970631760237469168">"Da napustite režim dem.?"</string>
     <string name="exit_retail_mode_dialog_body" msgid="8314316171782527301">"Ovim ćete izbrisati nalog za demonstracije i resetovati sistem na fabrička podešavanja. Svi podaci korisnika će biti izgubljeni."</string>
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"Napusti režim demonstr."</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"završite podešavanje"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ne sada"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funkcija nije dostupna tokom vožnje."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Ne možete da dodate korisnika tokom vožnje."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 995a66f..d5cbd6d 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Запыт на спалучэнне"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Націсніце, каб спалучыцца з прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Мовы"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Гук"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Гучнасць званка"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Гучнасць навігацыі"</string>
@@ -123,7 +122,7 @@
     <string name="firmware_version" msgid="8491753744549309333">"Версія Android"</string>
     <string name="security_patch" msgid="4794276590178386903">"Абнаўленне сістэмы бяспекі Android"</string>
     <string name="model_info" msgid="4966408071657934452">"Мадэль"</string>
-    <string name="baseband_version" msgid="2370088062235041897">"Версія модуля сувязі"</string>
+    <string name="baseband_version" msgid="2370088062235041897">"Версія дыяпазону"</string>
     <string name="kernel_version" msgid="7327212934187011508">"Версія ядра"</string>
     <string name="build_number" msgid="3997326631001009102">"Нумар зборкі"</string>
     <string name="device_info_not_available" msgid="2095601973977376655">"Недаступнае"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Выдаліць карыстальніка"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Новы карыстальнік"</string>
     <string name="user_guest" msgid="3465399481257448601">"Госць"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Адміністратар"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Вы ўвайшлі як адміністратар"</string>
     <string name="user_switch" msgid="6544839750534690781">"Пераключыцца"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Вы (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Імя"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Паўтарыць"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Прапусціць"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Задайце блакіроўку экрана"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Набярыце PIN-код"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Выберыце шаблон"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Набярыце пароль"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Бягучая блакіроўка экрана"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Задайце ўзор, каб абараніць прыладу"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Выдаліць"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Выдаліць"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Скасаваць"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Пацвердзіць"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Мінімальная колькасць сімвалаў: 4"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Пароль мае 4-8 сімвалаў, з іх 1 лічбу"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Мінімальная колькасць сімвалаў: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Мінімальная колькасць лічбаў PIN-кода: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Колькасць сімвалаў павінна быць менш за <xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Адміністратар прылады не дазваляе выкарыстоўваць апошні PIN-код"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"IT-адміністратар блакіруе папулярныя PIN-коды. Паспрабуйце іншы PIN-код."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Выкарыстанне недапушчальных сімвалаў."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Няправільны пароль. Мінімальная колькасць сімвалаў: 4"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Памылка. Пароль павінен складацца з 4-8 сімвалаў, утрымліваць як мінімум 1 лічбу, 1 літару, не ўтрымліваць прабелаў."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Павінен змяшчаць не менш за <xliff:g id="COUNT">%d</xliff:g> літару</item>
       <item quantity="few">Павінен змяшчаць не менш за <xliff:g id="COUNT">%d</xliff:g> літары</item>
@@ -315,5 +310,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"завяршыць наладку"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"не зараз"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Функцыя недаступная, калі вы за рулём."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Нельга дадаць карыстальніка, калі аўтамабіль рухаецца."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 606e882..d4a19dd 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Заявка за сдвояване"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Докоснете за сдвояване с/ъс „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Езици"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Звук"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Сила на звука при звънене"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Сила на звука на навигацията"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Изтриване на потребител"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Нов потребител"</string>
     <string name="user_guest" msgid="3465399481257448601">"Гост"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Администратор"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Влезли сте като администратор"</string>
     <string name="user_switch" msgid="6544839750534690781">"Превключване"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Вие (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Име"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Нов опит"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Пропускане"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Задайте опция за заключване на екрана"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Изберете ПИН кода си"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Изберете фигурата си"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Изберете паролата си"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Текуща опция за заключване на екрана"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"За по-голяма сигурност задайте фигура"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Изчистване"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Изчистване"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Отказ"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Потвърждаване"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Трябва да съдържа поне 4 знака"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Паролата трябва да съдържа между 4 и 8 знака с поне 1 цифра"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Трябва да съдържа поне <xliff:g id="COUNT">%d</xliff:g> знака"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"ПИН кодът трябва да съдържа поне <xliff:g id="COUNT">%d</xliff:g> цифри"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Трябва да съдържа по-малко от <xliff:g id="NUMBER">%d</xliff:g> знака"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Администраторът на устройството не разрешава ползването на скорошен ПИН"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Често срещаните ПИН кодове се блокират от системния ви администратор. Опитайте с друг ПИН код."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Не може да включва невалиден знак."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Паролата е невалидна, трябва да съдържа поне 4 знака."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Паролата е невалидна – трябва да съдържа между 4 и 8 знака с поне 1 цифра, 1 буква, без интервали."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Трябва да съдържа поне <xliff:g id="COUNT">%d</xliff:g> букви</item>
       <item quantity="one">Трябва да съдържа поне една буква</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"завършване на настройването"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"не сега"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Функцията не е налице по време на шофиране."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Потребителят не може да се добави по време на шофиране."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 5e73843..e6b8da8 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"পেয়ার করার অনুরোধ"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> এর সঙ্গে পেয়ার করতে ট্যাপ করুন।"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ভাষা"</string>
     <string name="sound_settings" msgid="3072423952331872246">"সাউন্ড"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"রিং ভলিউম"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"ন্যাভিগেশন ভলিউম"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ব্যবহারকারীকে মুছুন"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"নতুন ব্যবহারকারী"</string>
     <string name="user_guest" msgid="3465399481257448601">"অতিথি"</string>
-    <string name="user_admin" msgid="1535484812908584809">"অ্যাডমিন"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"অ্যাডমিন হিসেবে সাইন-ইন করেছেন"</string>
     <string name="user_switch" msgid="6544839750534690781">"পরিবর্তন করুন"</string>
     <string name="current_user_name" msgid="3813671533249316823">"আপনি (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"নাম"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"আবার চেষ্টা করুন"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"এড়িয়ে যান"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"একটি স্ক্রিন লক সেট করুন"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"আপনার পিন বেছে নিন"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"আপনার প্যাটার্ন বেছে নিন"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"আপনার পাসওয়ার্ড বেছে নিন"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"বর্তমান স্ক্রিন লক"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"নিরাপত্তার জন্য একটি প্যাটার্ন সেট করুন"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"সরান"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"সরান"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"বাতিল করুন"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"নিশ্চিত করুন"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"কমপক্ষে ৪টি অক্ষর থাকা আবশ্যক"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"পাসওয়ার্ডে কমপক্ষে ১টি নম্বর সহ ৪-৮টি অক্ষর থাকা আবশ্যক"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"কমপক্ষে <xliff:g id="COUNT">%d</xliff:g>টি অক্ষর থাকতে হবে"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"পিন কমপক্ষে <xliff:g id="COUNT">%d</xliff:g>টি সংখ্যার হতে হবে"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g>টি অক্ষরের থেকে কম হওয়া আবশ্যক"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ডিভাইস অ্যাডমিন একটি সাম্প্রতিক পিন ব্যবহার করতে দেয় না"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"একইধরনের পিন আপনার আইটি অ্যাডমিনের মাধ্যমে ব্লক করা হয়। অন্য একটি পিন ব্যবহার করার চেষ্টা করুন।"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"কোনও নিষিদ্ধ অক্ষর ব্যবহার করা যাবে না।"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"এই পাসওয়ার্ডটি ব্যবহার করা যাবে না, এতে অন্তত ৪টি অক্ষর থাকতে হবে।"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"পাসওয়ার্ডটি ব্যবহার করা যাবে না, এতে ৪-৮টি অক্ষর এবং অন্তত ১টি করে সংখ্যা ও অক্ষর থাকতে হবে, স্পেস ব্যবহার করা চলবে না।"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">কমপক্ষে <xliff:g id="COUNT">%d</xliff:g>টি অক্ষর থাকতে হবে</item>
       <item quantity="other">কমপক্ষে <xliff:g id="COUNT">%d</xliff:g>টি অক্ষর থাকতে হবে</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ডেমো মোড ছেড়ে যান"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"সেট-আপ শেষ করুন"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"এখনই নয়"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ড্রাইভ করার সময় এই বৈশিষ্ট্য উপলভ্য নয়।"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ড্রাইভ করার সময় ব্যবহারকারীকে যোগ করতে পারবেন না।"</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 23ea576..3b57593 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -50,10 +50,10 @@
     <string name="wifi_ip_address" msgid="3128140627890954061">"IP adresa"</string>
     <string name="access_point_tag_key" msgid="1517143378973053337">"access_point_tag_key"</string>
   <string-array name="wifi_signals">
-    <item msgid="4897376984576812606">"Slaba"</item>
-    <item msgid="2032262610626057081">"Zadovoljavajuća"</item>
-    <item msgid="3859756017461098953">"Dobra"</item>
-    <item msgid="1521103743353335724">"Odlična"</item>
+    <item msgid="4897376984576812606">"Loš"</item>
+    <item msgid="2032262610626057081">"Slab"</item>
+    <item msgid="3859756017461098953">"Dobar"</item>
+    <item msgid="1521103743353335724">"Odličan"</item>
   </string-array>
     <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"Bluetooth"</string>
     <string name="bluetooth_quick_toggle_summary" msgid="4390699431893305353">"Uključi Bluetooth"</string>
@@ -91,9 +91,8 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Zahtjev za uparivanje"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Dodirnite za uparivanje s uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Jezici"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Zvuk"</string>
-    <string name="ring_volume_title" msgid="3135241004980719442">"Jačina zvuka zvona"</string>
+    <string name="ring_volume_title" msgid="3135241004980719442">"Jačina zvona"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Jačina zvuka navigacije"</string>
     <string name="incoming_call_volume_title" msgid="6972117872424656876">"Melodija zvona"</string>
     <string name="notification_volume_title" msgid="6749411263197157876">"Obavještenje"</string>
@@ -171,8 +170,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Brisanje korisnika"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Novi korisnik"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gost"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Prijavljeni ste kao administrator"</string>
     <string name="user_switch" msgid="6544839750534690781">"Prebaci"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Vi (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Ime"</string>
@@ -215,9 +212,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Pokušaj ponovo"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Preskoči"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Postavljanje zaključavanja ekrana"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Odabir PIN-a"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Odaberite uzorak"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Odabir lozinke"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Trenutni način zaključavanja ekrana"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Postavite uzorak radi sigurnosti"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Obriši"</string>
@@ -250,7 +245,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Obriši"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Otkaži"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Potvrdi"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Mora sadržavati najmanje 4 znaka"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Lozinka mora imati između 4 i 8 znakova od čega je najmanje jedan broj."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Broj znakova mora biti najmanje <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Broj cifri u PIN-u mora biti najmanje <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Ukupan broj znakova mora biti manji od <xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -259,7 +254,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Administrator uređaja ne dozvoljava korištenje nedavnog PIN kôda"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Vaš IT administrator je blokirao uobičajene PIN kôdove. Probajte drugi PIN kôd."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Ne može sadržavati nevažeći znak."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Lozinka je nevažeća. Mora sadržavati najmanje 4 znaka."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Lozinka je nevažeća, mora sadržavati između 4 i 8 znakova, najmanje jedan broj, jedno slovo i ne smije sadržavati razmake."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> slovo</item>
       <item quantity="few">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> slova</item>
@@ -308,5 +303,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"dovrši postavljanje"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ne sada"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funkcija nije dostupna tokom vožnje."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Nije moguće dodati korisnika za vrijeme vožnje."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 2c70044..5691e78 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -22,9 +22,9 @@
     <string name="more_settings_label" msgid="3867559443480110616">"Més"</string>
     <string name="display_settings" msgid="5325515247739279185">"Pantalla"</string>
     <string name="brightness" msgid="2919605130898772866">"Nivell de brillantor"</string>
-    <string name="auto_brightness_title" msgid="9124647862844666581">"Brillantor adaptativa"</string>
+    <string name="auto_brightness_title" msgid="9124647862844666581">"Brillantor automàtica"</string>
     <string name="auto_brightness_summary" msgid="4741887033140384352">"Optimitza el nivell de brillantor segons la llum disponible"</string>
-    <string name="condition_night_display_title" msgid="3777509730126972675">"Llum nocturna activada"</string>
+    <string name="condition_night_display_title" msgid="3777509730126972675">"Opció Llum nocturna activada"</string>
     <string name="keywords_display" msgid="3978416985146943922">"pantalla, pantalla tàctil"</string>
     <string name="keywords_display_brightness_level" msgid="3956411572536209195">"pantalla atenuada, pantalla tàctil, bateria"</string>
     <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"pantalla atenuada, pantalla tàctil, bateria"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Sol·licitud de vinculació"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Toca per vincular el dispositiu amb <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Idiomes"</string>
     <string name="sound_settings" msgid="3072423952331872246">"So"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volum del to"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volum de navegació"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Suprimeix l\'usuari"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Usuari nou"</string>
     <string name="user_guest" msgid="3465399481257448601">"Convidat"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrador"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Sessió iniciada com a administrador"</string>
     <string name="user_switch" msgid="6544839750534690781">"Canvia"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Tu (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nom"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Torna-ho a provar"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Omet"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Defineix un bloqueig de pantalla"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Tria un PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Tria el teu patró"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Tria una contrasenya"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Bloqueig de pantalla actual"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Per seguretat, defineix un patró"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Esborra"</string>
@@ -226,7 +221,7 @@
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Deixa anar el dit quan acabis"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"Patró enregistrat"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"Repeteix el patró per confirmar-lo"</string>
-    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Connecta 4 punts com a mínim. Torna-ho a provar."</string>
+    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Connecta almenys 4 punts. Torna a provar."</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"El patró no és correcte"</string>
     <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"Com es dibuixa un patró de desbloqueig"</string>
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"S\'ha produït un error en desar el patró"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Esborra"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancel·la"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirma"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Ha de tenir com a mínim 4 caràcters"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"La contrasenya ha de tenir 4-8 caràcters i com a mínim 1 número"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Ha de tenir com a mínim <xliff:g id="COUNT">%d</xliff:g> caràcters"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"El PIN ha de tenir com a mínim <xliff:g id="COUNT">%d</xliff:g> dígits"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Ha de tenir <xliff:g id="NUMBER">%d</xliff:g> caràcters o menys"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"L\'administrador del dispositiu no permet que s\'utilitzi un PIN recent"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"L\'administrador de TI ha bloquejat els PIN més comuns. Prova un altre PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"No pot incloure un caràcter no vàlid."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"La contrasenya no és vàlida; ha de tenir com a mínim 4 caràcters."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"La contrasenya no és vàlida. Ha de tenir entre 4 i 8 caràcters, ha d\'incloure com a mínim 1 dígit i 1 lletra i no pot tenir cap espai en blanc."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Ha de contenir com a mínim <xliff:g id="COUNT">%d</xliff:g> lletres</item>
       <item quantity="one">Ha de contenir com a mínim 1 lletra</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"finalitza la configuració"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ara no"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Aquesta funció no està disponible mentre condueixes."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"No pots afegir usuaris mentre condueixes."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index f7b6ae5..69535e2 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Požadavek na párování"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Klepnutím spárujete se zařízením <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Jazyky"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Zvuk"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Hlasitost vyzvánění"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Hlasitost navigace"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Smazání uživatele"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nový uživatel"</string>
     <string name="user_guest" msgid="3465399481257448601">"Host"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrátor"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Jste přihlášeni jako administrátor"</string>
     <string name="user_switch" msgid="6544839750534690781">"Přepnout"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Vy (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Jméno"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Zkusit znovu"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Přeskočit"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Nastavení zámku obrazovky"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Zvolte kód PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Vyberte gesto"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Zvolte heslo"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Aktuální zámek obrazovky"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Z bezpečnostních důvodů nastavte gesto"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Vymazat"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Vymazat"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Zrušit"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Potvrdit"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Musí obsahovat nejméně 4 znaky"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Heslo musí mít čtyři až osm znaků a obsahovat alespoň jednu číslici."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Minimální počet znaků: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Minimální počet číslic kódu PIN: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Heslo musí být kratší než <xliff:g id="NUMBER">%d</xliff:g> znaků"</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Administrátor zařízení nedovoluje použití nedávno použitého kódu PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Obvyklé kódy PIN jsou blokovány administrátorem IT. Použijte jiný PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Heslo nesmí obsahovat neplatné znaky."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Heslo není platné, musí obsahovat nejméně 4 znaky."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Heslo je neplatné. Musí mít čtyři až osm znaků, obsahovat alespoň jednu číslici, jedno písmeno a žádné mezery."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="few">Heslo musí obsahovat alespoň <xliff:g id="COUNT">%d</xliff:g> písmena</item>
       <item quantity="many">Heslo musí obsahovat alespoň <xliff:g id="COUNT">%d</xliff:g> písmena</item>
@@ -315,5 +310,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"dokončit nastavení"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"teď ne"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funkce při řízení není dostupná."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Při řízení nelze přidat uživatele."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 15af528..3692cc7 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -22,14 +22,14 @@
     <string name="more_settings_label" msgid="3867559443480110616">"Flere"</string>
     <string name="display_settings" msgid="5325515247739279185">"Visning"</string>
     <string name="brightness" msgid="2919605130898772866">"Lysstyrke"</string>
-    <string name="auto_brightness_title" msgid="9124647862844666581">"Automatisk lysstyrke"</string>
+    <string name="auto_brightness_title" msgid="9124647862844666581">"Selvjusterende lysstyrke"</string>
     <string name="auto_brightness_summary" msgid="4741887033140384352">"Optimer lysstyrken til det omgivende lys"</string>
     <string name="condition_night_display_title" msgid="3777509730126972675">"Nattelys er aktiveret"</string>
     <string name="keywords_display" msgid="3978416985146943922">"skærm, touchskærm"</string>
     <string name="keywords_display_brightness_level" msgid="3956411572536209195">"nedton skærm, touchskærm, batteri"</string>
     <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"nedton skærm, touchskærm, batteri"</string>
     <string name="keywords_display_night_display" msgid="2922294576679769957">"nedton skærm, nat, farvetone"</string>
-    <string name="night_mode_tile_label" msgid="6603597795502131664">"Nattilstand"</string>
+    <string name="night_mode_tile_label" msgid="6603597795502131664">"Natfunktion"</string>
     <string name="wifi_settings" msgid="7701477685273103841">"Wi-Fi"</string>
     <string name="wifi_settings_summary" msgid="6095898149997291025">"Konfigurer og administrer trådløse adgangspunkter"</string>
     <string name="wifi_starting" msgid="473253087503153167">"Aktiverer Wi-Fi…"</string>
@@ -77,13 +77,13 @@
     <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"Brug til"</string>
     <string name="wifi_ssid_hint" msgid="4155050863239489553">"Skift navn for Bluetooth-enheden"</string>
     <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"Anmodning om Bluetooth-parring"</string>
-    <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"Tilknyt og forbind"</string>
+    <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"Dan par, og forbind"</string>
     <string name="bluetooth" msgid="5235115159234688629">"Bluetooth"</string>
     <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"Bluetooth-parringskode"</string>
     <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"Pinkoden indeholder bogstaver eller symboler"</string>
     <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"Angiv parringskoden, og tryk på Enter"</string>
     <string name="bluetooth_pairing_request" msgid="4769675459526556801">"Vil du parre med <xliff:g id="DEVICE_NAME">%1$s</xliff:g>?"</string>
-    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"Giv <xliff:g id="DEVICE_NAME">%1$s</xliff:g> adgang til dine kontakter og din opkaldshistorik"</string>
+    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"Giv <xliff:g id="DEVICE_NAME">%1$s</xliff:g> adgang til dine kontaktpersoner og din opkaldshistorik"</string>
     <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"Du skal muligvis også indtaste denne pinkode på den anden enhed."</string>
     <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"Du skal muligvis også indtaste denne adgangsnøgle på den anden enhed."</string>
     <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"Skal bestå af 16 cifre"</string>
@@ -91,12 +91,11 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Anmodning om parring"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tryk for at parre med <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Sprog"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Lyd"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Lydstyrke for ringetone"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigationslydstyrke"</string>
     <string name="incoming_call_volume_title" msgid="6972117872424656876">"Ringetone"</string>
-    <string name="notification_volume_title" msgid="6749411263197157876">"Notifikation"</string>
+    <string name="notification_volume_title" msgid="6749411263197157876">"Underretning"</string>
     <string name="media_volume_title" msgid="6697416686272606865">"Medier"</string>
     <string name="media_volume_summary" msgid="2961762827637127239">"Angiv lydstyrke for musik og videoer"</string>
     <string name="alarm_volume_title" msgid="840384014895796587">"Alarm"</string>
@@ -108,7 +107,7 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Der er ikke givet nogen tilladelser"</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Der er ikke anmodet om nogen tilladelser"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Dataforbrug"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Dataforbrug i apps"</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Appens dataforbrug"</string>
     <string name="force_stop" msgid="2153183697014720520">"Tving til at standse"</string>
     <string name="computing_size" msgid="5791407621793083965">"Beregner…"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
@@ -119,7 +118,7 @@
     <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"Systemopdateringer"</string>
     <string name="system_update_settings_list_item_summary" msgid="7395202602021608371"></string>
     <string name="firmware_version" msgid="8491753744549309333">"Android-version"</string>
-    <string name="security_patch" msgid="4794276590178386903">"Seneste sikkerhedsopdatering i Android"</string>
+    <string name="security_patch" msgid="4794276590178386903">"Niveau for sikkerhedsrettelse på Android"</string>
     <string name="model_info" msgid="4966408071657934452">"Model"</string>
     <string name="baseband_version" msgid="2370088062235041897">"Basebandversion"</string>
     <string name="kernel_version" msgid="7327212934187011508">"Kernesystem"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Slet bruger"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Ny bruger"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gæst"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Logget ind som administrator"</string>
     <string name="user_switch" msgid="6544839750534690781">"Skift"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Dig (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Navn"</string>
@@ -188,7 +185,7 @@
     <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"Begrænsede profiler kan ikke tilføje konti"</string>
     <string name="remove_account_title" msgid="8840386525787836381">"Fjern konto"</string>
     <string name="really_remove_account_title" msgid="3555164432587924900">"Vil du fjerne kontoen?"</string>
-    <string name="really_remove_account_message" msgid="4296769280849579900">"Hvis du fjerner denne konto, slettes alle tilknyttede beskeder, kontakter og andre data fra enheden."</string>
+    <string name="really_remove_account_message" msgid="4296769280849579900">"Hvis du fjerner denne konto, slettes alle tilknyttede beskeder, kontaktpersoner og andre data fra enheden."</string>
     <string name="remove_account_failed" msgid="7472511529086294087">"Din administrator har ikke givet tilladelse til at foretage denne ændring"</string>
     <string name="really_remove_user_title" msgid="4990029019291756762">"Vil du fjerne brugeren?"</string>
     <string name="really_remove_user_message" msgid="3828090902833944533">"Alle apps og data slettes."</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Prøv igen"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Spring over"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Konfigurer en skærmlås"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Vælg din pinkode"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Vælg dit mønster"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Vælg din adgangskode"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Aktuel skærmlås"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Angiv et mønster af sikkerhedshensyn"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Ryd"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Ryd"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Annuller"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Bekræft"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Adgangskoden skal fylde mindst 4 tegn"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Krav til adgangskode: 4-8 tegn, heraf mindst 1 tal"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Adgangskoden skal indeholde min. <xliff:g id="COUNT">%d</xliff:g> tegn"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Pinkoden skal indeholde min. <xliff:g id="COUNT">%d</xliff:g> tal"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Adgangskoden må indeholde maks. <xliff:g id="NUMBER">%d</xliff:g> tegn"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Enhedens administrator tillader ikke brug af en nylig brugt pinkode"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Brug af almindelige pinkoder er blokeret af din it-administrator. Prøv en anden pinkode."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Der må ikke bruges et ugyldigt tegn."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Adgangskoden er ugyldig. Den skal fylde mindst 4 tegn."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Adgangskoden er ugyldig. Den skal indeholde 4-8 tegn, heraf mindst 1 tal og 1 bogstav. Den må ikke indeholde mellemrum."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Skal indeholde mindst <xliff:g id="COUNT">%d</xliff:g> bogstav</item>
       <item quantity="other">Skal indeholde mindst <xliff:g id="COUNT">%d</xliff:g> bogstaver</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"afslut konfiguration"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ikke nu"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funktionen er ikke tilgængelig under kørsel."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Du kan ikke tilføje brugere under kørsel."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 6c6c71f..e37a454 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -22,7 +22,7 @@
     <string name="more_settings_label" msgid="3867559443480110616">"Mehr"</string>
     <string name="display_settings" msgid="5325515247739279185">"Display"</string>
     <string name="brightness" msgid="2919605130898772866">"Helligkeit"</string>
-    <string name="auto_brightness_title" msgid="9124647862844666581">"Automatische Helligkeit"</string>
+    <string name="auto_brightness_title" msgid="9124647862844666581">"Adaptive Helligkeit"</string>
     <string name="auto_brightness_summary" msgid="4741887033140384352">"Helligkeit an Lichtverhältnisse anpassen"</string>
     <string name="condition_night_display_title" msgid="3777509730126972675">"Nachtlicht ist eingeschaltet"</string>
     <string name="keywords_display" msgid="3978416985146943922">"bildschirm, display, touchscreen"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Kopplungsanfrage"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Zum Koppeln mit <xliff:g id="DEVICE_NAME">%1$s</xliff:g> tippen."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Sprachen"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Ton"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Klingeltonlautstärke"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Lautstärke für Navigation"</string>
@@ -108,7 +107,7 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Keine Berechtigungen gewährt"</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Keine Berechtigungen angefordert"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Datennutzung"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Datenverbrauch durch Apps"</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Datennutzung durch Apps"</string>
     <string name="force_stop" msgid="2153183697014720520">"Beenden erzwingen"</string>
     <string name="computing_size" msgid="5791407621793083965">"Berechnung läuft…"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
@@ -143,7 +142,7 @@
     <string name="webview_license_title" msgid="2531829466541104826">"System-WebView-Lizenz"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"Hintergründe"</string>
     <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Satellitenbilder bereitgestellt von:\n© 2014 CNES/Astrium, DigitalGlobe, Bluesky"</string>
-    <string name="settings_license_activity_title" msgid="8499293744313077709">"Lizenzen Dritter"</string>
+    <string name="settings_license_activity_title" msgid="8499293744313077709">"Drittlizenzen"</string>
     <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"Beim Laden der Lizenzen ist ein Problem aufgetreten."</string>
     <string name="settings_license_activity_loading" msgid="6163263123009681841">"Wird geladen…"</string>
     <string name="date_and_time_settings_title" msgid="4058492663544475485">"Datum &amp; Uhrzeit"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Nutzer löschen"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Neuer Nutzer"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gast"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Als Administrator angemeldet"</string>
     <string name="user_switch" msgid="6544839750534690781">"Wechseln"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Du (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Name"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Wiederholen"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Überspringen"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Displaysperre einrichten"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PIN wählen"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Muster wählen"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Passwort ändern"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Aktuelle Displaysperre"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Richte zur Sicherheit ein Muster ein"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Löschen"</string>
@@ -230,7 +225,7 @@
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"Falsches Muster"</string>
     <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"Zeichnen eines Entsperrungsmusters"</string>
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"Fehler beim Speichern des Musters"</string>
-    <string name="okay" msgid="4589873324439764349">"Ok"</string>
+    <string name="okay" msgid="4589873324439764349">"OK"</string>
     <string name="remove_screen_lock_title" msgid="1234382338764193387">"Displaysperre entfernen?"</string>
     <string name="remove_screen_lock_message" msgid="6675850371585564965">"Dadurch können andere auf dein Konto zugreifen"</string>
     <string name="lock_settings_enter_pin" msgid="1669172111244633904">"PIN eingeben"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Löschen"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Abbrechen"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Bestätigen"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Muss mindestens 4 Zeichen haben"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Passwort muss 4–8 Zeichen und mindestens 1 Zahl enthalten"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Passwort muss mindestens <xliff:g id="COUNT">%d</xliff:g> Zeichen haben"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN muss mindestens <xliff:g id="COUNT">%d</xliff:g> Ziffern haben"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Passwort muss weniger als <xliff:g id="NUMBER">%d</xliff:g> Zeichen haben"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Der Geräteadministrator hat festgelegt, dass bereits verwendete PINs nicht noch einmal verwendet werden dürfen"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Dein IT-Administrator hat die Verwendung allzu häufiger PINs blockiert. Versuch es mit einer anderen PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Ungültige Zeichen sind nicht zulässig."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Ungültiges Passwort – muss mindestens 4 Zeichen haben."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Passwort ungültig. Es muss 4–8 Zeichen lang sein und mindestens eine Zahl und einen Buchstaben enthalten. Leerzeichen sind nicht zulässig."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Passwort muss mindestens <xliff:g id="COUNT">%d</xliff:g> Buchstaben enthalten</item>
       <item quantity="one">Passwort muss mindestens einen Buchstaben enthalten</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"Einrichtung abschließen"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"jetzt nicht"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funktion während der Fahrt nicht verfügbar."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Während der Fahrt kann kein Nutzer hinzugefügt werden."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 93238c1..05a497b 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Αίτημα σύζευξης"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Πατήστε για σύζευξη με τη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Γλώσσες"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Ήχος"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Ένταση ήχου κουδουνίσματος"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Ένταση ήχου πλοήγησης"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Διαγραφή χρήστη"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Νέος χρήστης"</string>
     <string name="user_guest" msgid="3465399481257448601">"Επισκέπτης"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Διαχειριστής"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Συνδεδεμένος ως διαχειριστής"</string>
     <string name="user_switch" msgid="6544839750534690781">"Εναλλαγή"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Εσείς (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Όνομα"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Επανάληψη"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Παράβλεψη"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ορισμός κλειδώματος οθόνης"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Επιλέξτε αριθμό PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Επιλέξτε το μοτίβο σας"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Επιλέξτε κωδικό πρόσβασης"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Τρέχουσα οθόνη κλειδώματος"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Για λόγους ασφαλείας, ορίστε ένα μοτίβο"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Διαγραφή"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Διαγραφή"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Ακύρωση"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Επιβεβαίωση"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Πρέπει να αποτελείται τουλάχιστον από 4 χαρακτήρες"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Ο κωδ. πρόσβ. πρέπει να έχει 4-8 χαρακτ. και τουλάχ. 1 αριθμό."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Πρέπει να έχει τουλάχιστον <xliff:g id="COUNT">%d</xliff:g> χαρακτήρες"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Το PIN πρέπει να έχει τουλάχιστον <xliff:g id="COUNT">%d</xliff:g> ψηφία"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Πρέπει να έχει λιγότερ. από <xliff:g id="NUMBER">%d</xliff:g> χαρακτήρες"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Ο διαχειριστής της συσκευής δεν επιτρέπει τη χρήση πρόσφατου PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Τα συνηθισμένα PIN αποκλείονται από τον διαχειριστή IT. Δοκιμάστε να χρησιμοποιήσετε διαφορετικό PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Δεν μπορεί να περιλαμβάνει μη έγκυρο χαρακτήρα."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Μη έγκυρος κωδικός πρόσβασης. Πρέπει να αποτελείται από τουλάχιστον 4 χαρακτήρες."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Ο κωδικός πρόσβασης δεν είναι έγκυρος. Πρέπει να αποτελείται από 4-8 χαρακτήρες, να περιέχει τουλάχιστον 1 ψηφίο, 1 γράμμα και κανένα κενό διάστημα."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Πρέπει να περιέχει τουλάχιστον <xliff:g id="COUNT">%d</xliff:g> γράμματα</item>
       <item quantity="one">Πρέπει να περιέχει τουλάχιστον 1 γράμμα</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ολοκλήρωση ρύθμισης"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"όχι τώρα"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Η λειτουργία δεν διατίθεται κατά τη διάρκεια της οδήγησης."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Δεν είναι δυνατή η προσθήκη χρήστη κατά την οδήγηση."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index db58240..2e0d1d6 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Pairing request"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tap to pair with <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Languages"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Sound"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Ring volume"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigation volume"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Delete user"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"New user"</string>
     <string name="user_guest" msgid="3465399481257448601">"Guest"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Admin"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Signed in as admin"</string>
     <string name="user_switch" msgid="6544839750534690781">"Switch"</string>
     <string name="current_user_name" msgid="3813671533249316823">"You (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Name"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Retry"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Skip"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Set a screen lock"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Choose your PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Choose your pattern"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Choose your passowrd"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Current screen lock"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"For security, set a pattern"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Clear"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Clear"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancel"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirm"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Must be at least four characters"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Password must be between 4–8 characters with at least 1 number"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Must be at least <xliff:g id="COUNT">%d</xliff:g> characters"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN must be at least <xliff:g id="COUNT">%d</xliff:g> digits"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Must be fewer than <xliff:g id="NUMBER">%d</xliff:g> characters"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Device admin doesn\'t allow using a recent PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Common PINs are blocked by your IT admin. Try a different PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"This can\'t include an invalid character."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Password invalid. Must be at least four characters."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Password invalid. Must be 4-8 characters, contain at least 1 digit, 1 letter, no whitespace."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> letters</item>
       <item quantity="one">Must contain at least one letter</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"finish setup"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"not now"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Feature not available while driving."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Can\'t add user while driving."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index db58240..2e0d1d6 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Pairing request"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tap to pair with <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Languages"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Sound"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Ring volume"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigation volume"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Delete user"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"New user"</string>
     <string name="user_guest" msgid="3465399481257448601">"Guest"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Admin"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Signed in as admin"</string>
     <string name="user_switch" msgid="6544839750534690781">"Switch"</string>
     <string name="current_user_name" msgid="3813671533249316823">"You (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Name"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Retry"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Skip"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Set a screen lock"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Choose your PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Choose your pattern"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Choose your passowrd"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Current screen lock"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"For security, set a pattern"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Clear"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Clear"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancel"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirm"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Must be at least four characters"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Password must be between 4–8 characters with at least 1 number"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Must be at least <xliff:g id="COUNT">%d</xliff:g> characters"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN must be at least <xliff:g id="COUNT">%d</xliff:g> digits"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Must be fewer than <xliff:g id="NUMBER">%d</xliff:g> characters"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Device admin doesn\'t allow using a recent PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Common PINs are blocked by your IT admin. Try a different PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"This can\'t include an invalid character."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Password invalid. Must be at least four characters."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Password invalid. Must be 4-8 characters, contain at least 1 digit, 1 letter, no whitespace."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> letters</item>
       <item quantity="one">Must contain at least one letter</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"finish setup"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"not now"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Feature not available while driving."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Can\'t add user while driving."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index db58240..2e0d1d6 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Pairing request"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tap to pair with <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Languages"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Sound"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Ring volume"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigation volume"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Delete user"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"New user"</string>
     <string name="user_guest" msgid="3465399481257448601">"Guest"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Admin"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Signed in as admin"</string>
     <string name="user_switch" msgid="6544839750534690781">"Switch"</string>
     <string name="current_user_name" msgid="3813671533249316823">"You (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Name"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Retry"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Skip"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Set a screen lock"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Choose your PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Choose your pattern"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Choose your passowrd"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Current screen lock"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"For security, set a pattern"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Clear"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Clear"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancel"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirm"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Must be at least four characters"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Password must be between 4–8 characters with at least 1 number"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Must be at least <xliff:g id="COUNT">%d</xliff:g> characters"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN must be at least <xliff:g id="COUNT">%d</xliff:g> digits"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Must be fewer than <xliff:g id="NUMBER">%d</xliff:g> characters"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Device admin doesn\'t allow using a recent PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Common PINs are blocked by your IT admin. Try a different PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"This can\'t include an invalid character."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Password invalid. Must be at least four characters."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Password invalid. Must be 4-8 characters, contain at least 1 digit, 1 letter, no whitespace."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> letters</item>
       <item quantity="one">Must contain at least one letter</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"finish setup"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"not now"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Feature not available while driving."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Can\'t add user while driving."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index db58240..2e0d1d6 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Pairing request"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tap to pair with <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Languages"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Sound"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Ring volume"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigation volume"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Delete user"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"New user"</string>
     <string name="user_guest" msgid="3465399481257448601">"Guest"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Admin"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Signed in as admin"</string>
     <string name="user_switch" msgid="6544839750534690781">"Switch"</string>
     <string name="current_user_name" msgid="3813671533249316823">"You (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Name"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Retry"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Skip"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Set a screen lock"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Choose your PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Choose your pattern"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Choose your passowrd"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Current screen lock"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"For security, set a pattern"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Clear"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Clear"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancel"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirm"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Must be at least four characters"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Password must be between 4–8 characters with at least 1 number"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Must be at least <xliff:g id="COUNT">%d</xliff:g> characters"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN must be at least <xliff:g id="COUNT">%d</xliff:g> digits"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Must be fewer than <xliff:g id="NUMBER">%d</xliff:g> characters"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Device admin doesn\'t allow using a recent PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Common PINs are blocked by your IT admin. Try a different PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"This can\'t include an invalid character."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Password invalid. Must be at least four characters."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Password invalid. Must be 4-8 characters, contain at least 1 digit, 1 letter, no whitespace."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Must contain at least <xliff:g id="COUNT">%d</xliff:g> letters</item>
       <item quantity="one">Must contain at least one letter</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"finish setup"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"not now"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Feature not available while driving."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Can\'t add user while driving."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 5471ef7..0bfa173 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -18,288 +18,284 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="settings_label" msgid="5147911978211079839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎Settings‎‏‎‎‏‎"</string>
-    <string name="more_settings_label" msgid="3867559443480110616">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎More‎‏‎‎‏‎"</string>
-    <string name="display_settings" msgid="5325515247739279185">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‎Display‎‏‎‎‏‎"</string>
-    <string name="brightness" msgid="2919605130898772866">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎Brightness level‎‏‎‎‏‎"</string>
-    <string name="auto_brightness_title" msgid="9124647862844666581">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎Adaptive brightness‎‏‎‎‏‎"</string>
-    <string name="auto_brightness_summary" msgid="4741887033140384352">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎Optimize brightness level for available light‎‏‎‎‏‎"</string>
-    <string name="condition_night_display_title" msgid="3777509730126972675">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎Night Light is on‎‏‎‎‏‎"</string>
-    <string name="keywords_display" msgid="3978416985146943922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‎screen, touchscreen‎‏‎‎‏‎"</string>
-    <string name="keywords_display_brightness_level" msgid="3956411572536209195">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎dim screen, touchscreen, battery‎‏‎‎‏‎"</string>
-    <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎dim screen, touchscreen, battery‎‏‎‎‏‎"</string>
-    <string name="keywords_display_night_display" msgid="2922294576679769957">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‎dim screen, night, tint‎‏‎‎‏‎"</string>
-    <string name="night_mode_tile_label" msgid="6603597795502131664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎‎Night mode‎‏‎‎‏‎"</string>
-    <string name="wifi_settings" msgid="7701477685273103841">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‎Wi‑Fi‎‏‎‎‏‎"</string>
-    <string name="wifi_settings_summary" msgid="6095898149997291025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎Set up &amp; manage wireless access points‎‏‎‎‏‎"</string>
-    <string name="wifi_starting" msgid="473253087503153167">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‎Turning Wi‑Fi on…‎‏‎‎‏‎"</string>
-    <string name="wifi_stopping" msgid="3534173972547890148">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎Turning off Wi‑Fi…‎‏‎‎‏‎"</string>
-    <string name="wifi_failed_forget_message" msgid="121732682699377206">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‎‎Failed to forget network‎‏‎‎‏‎"</string>
-    <string name="wifi_failed_connect_message" msgid="4447498225022147324">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‎‎Failed to connect to network‎‏‎‎‏‎"</string>
-    <string name="wifi_setup_add_network" msgid="3660498520389954620">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎Add network‎‏‎‎‏‎"</string>
-    <string name="wifi_disabled" msgid="5013262438128749950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‎Wi‑Fi disabled‎‏‎‎‏‎"</string>
-    <string name="wifi_setup_connect" msgid="3512399573397979101">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎Connect‎‏‎‎‏‎"</string>
-    <string name="wifi_password" msgid="5565632142720292397">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎Password‎‏‎‎‏‎"</string>
-    <string name="wifi_show_password" msgid="8423293211933521097">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‏‏‎‎‏‎‎‏‎Show password‎‏‎‎‏‎"</string>
-    <string name="wifi_ssid" msgid="488604828159458741">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎Network name‎‏‎‎‏‎"</string>
-    <string name="wifi_security" msgid="158358046038876532">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎Security‎‏‎‎‏‎"</string>
-    <string name="wifi_signal" msgid="1817579728350364549">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‎‏‎Signal strength‎‏‎‎‏‎"</string>
-    <string name="wifi_status" msgid="5688013206066543952">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎Status‎‏‎‎‏‎"</string>
-    <string name="wifi_speed" msgid="1650692446731850781">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎Link speed‎‏‎‎‏‎"</string>
-    <string name="wifi_frequency" msgid="8951455949682864922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎Frequency‎‏‎‎‏‎"</string>
-    <string name="wifi_ip_address" msgid="3128140627890954061">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎IP address‎‏‎‎‏‎"</string>
-    <string name="access_point_tag_key" msgid="1517143378973053337">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎access_point_tag_key‎‏‎‎‏‎"</string>
+    <string name="settings_label" msgid="5147911978211079839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎Settings‎‏‎‎‏‎"</string>
+    <string name="more_settings_label" msgid="3867559443480110616">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎More‎‏‎‎‏‎"</string>
+    <string name="display_settings" msgid="5325515247739279185">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‎Display‎‏‎‎‏‎"</string>
+    <string name="brightness" msgid="2919605130898772866">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎Brightness level‎‏‎‎‏‎"</string>
+    <string name="auto_brightness_title" msgid="9124647862844666581">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎Adaptive brightness‎‏‎‎‏‎"</string>
+    <string name="auto_brightness_summary" msgid="4741887033140384352">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎Optimize brightness level for available light‎‏‎‎‏‎"</string>
+    <string name="condition_night_display_title" msgid="3777509730126972675">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎Night Light is on‎‏‎‎‏‎"</string>
+    <string name="keywords_display" msgid="3978416985146943922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‎screen, touchscreen‎‏‎‎‏‎"</string>
+    <string name="keywords_display_brightness_level" msgid="3956411572536209195">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎dim screen, touchscreen, battery‎‏‎‎‏‎"</string>
+    <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎dim screen, touchscreen, battery‎‏‎‎‏‎"</string>
+    <string name="keywords_display_night_display" msgid="2922294576679769957">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‎dim screen, night, tint‎‏‎‎‏‎"</string>
+    <string name="night_mode_tile_label" msgid="6603597795502131664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎‎Night mode‎‏‎‎‏‎"</string>
+    <string name="wifi_settings" msgid="7701477685273103841">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‎Wi‑Fi‎‏‎‎‏‎"</string>
+    <string name="wifi_settings_summary" msgid="6095898149997291025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎Set up &amp; manage wireless access points‎‏‎‎‏‎"</string>
+    <string name="wifi_starting" msgid="473253087503153167">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‎Turning Wi‑Fi on…‎‏‎‎‏‎"</string>
+    <string name="wifi_stopping" msgid="3534173972547890148">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎Turning off Wi‑Fi…‎‏‎‎‏‎"</string>
+    <string name="wifi_failed_forget_message" msgid="121732682699377206">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‎‎Failed to forget network‎‏‎‎‏‎"</string>
+    <string name="wifi_failed_connect_message" msgid="4447498225022147324">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‎‎Failed to connect to network‎‏‎‎‏‎"</string>
+    <string name="wifi_setup_add_network" msgid="3660498520389954620">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎Add network‎‏‎‎‏‎"</string>
+    <string name="wifi_disabled" msgid="5013262438128749950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‎Wi‑Fi disabled‎‏‎‎‏‎"</string>
+    <string name="wifi_setup_connect" msgid="3512399573397979101">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎Connect‎‏‎‎‏‎"</string>
+    <string name="wifi_password" msgid="5565632142720292397">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎Password‎‏‎‎‏‎"</string>
+    <string name="wifi_show_password" msgid="8423293211933521097">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‏‏‎‎‏‎‎‏‎Show password‎‏‎‎‏‎"</string>
+    <string name="wifi_ssid" msgid="488604828159458741">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎Network name‎‏‎‎‏‎"</string>
+    <string name="wifi_security" msgid="158358046038876532">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎Security‎‏‎‎‏‎"</string>
+    <string name="wifi_signal" msgid="1817579728350364549">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‎‏‎Signal strength‎‏‎‎‏‎"</string>
+    <string name="wifi_status" msgid="5688013206066543952">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎Status‎‏‎‎‏‎"</string>
+    <string name="wifi_speed" msgid="1650692446731850781">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎Link speed‎‏‎‎‏‎"</string>
+    <string name="wifi_frequency" msgid="8951455949682864922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎Frequency‎‏‎‎‏‎"</string>
+    <string name="wifi_ip_address" msgid="3128140627890954061">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎IP address‎‏‎‎‏‎"</string>
+    <string name="access_point_tag_key" msgid="1517143378973053337">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎access_point_tag_key‎‏‎‎‏‎"</string>
   <string-array name="wifi_signals">
-    <item msgid="4897376984576812606">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‎Poor‎‏‎‎‏‎"</item>
-    <item msgid="2032262610626057081">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎Fair‎‏‎‎‏‎"</item>
-    <item msgid="3859756017461098953">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‎Good‎‏‎‎‏‎"</item>
-    <item msgid="1521103743353335724">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎Excellent‎‏‎‎‏‎"</item>
+    <item msgid="4897376984576812606">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‎Poor‎‏‎‎‏‎"</item>
+    <item msgid="2032262610626057081">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎Fair‎‏‎‎‏‎"</item>
+    <item msgid="3859756017461098953">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‎Good‎‏‎‎‏‎"</item>
+    <item msgid="1521103743353335724">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎Excellent‎‏‎‎‏‎"</item>
   </string-array>
-    <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎Bluetooth‎‏‎‎‏‎"</string>
-    <string name="bluetooth_quick_toggle_summary" msgid="4390699431893305353">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‎‏‎Turn on Bluetooth‎‏‎‎‏‎"</string>
-    <string name="bluetooth_settings" msgid="3878243366013638982">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎Bluetooth‎‏‎‎‏‎"</string>
-    <string name="bluetooth_disabled" msgid="4187409401590350572">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎Bluetooth disabled‎‏‎‎‏‎"</string>
-    <string name="bluetooth_settings_title" msgid="3794688574569688649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎‎‏‎‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‎Bluetooth‎‏‎‎‏‎"</string>
-    <string name="bluetooth_settings_summary" msgid="4023303473646769835">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‏‏‎Manage connections, set device name &amp; discoverability‎‏‎‎‏‎"</string>
-    <string name="bluetooth_talkback_computer" msgid="5223330129934365312">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎Computer‎‏‎‎‏‎"</string>
-    <string name="bluetooth_talkback_headset" msgid="6155254514321149935">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‏‎Headset‎‏‎‎‏‎"</string>
-    <string name="bluetooth_talkback_phone" msgid="8833977851215000426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎Phone‎‏‎‎‏‎"</string>
-    <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‎Imaging‎‏‎‎‏‎"</string>
-    <string name="bluetooth_talkback_headphone" msgid="5362155791551671490">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‎Headphone‎‏‎‎‏‎"</string>
-    <string name="bluetooth_talkback_input_peripheral" msgid="868933277567862622">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‎‎Input Peripheral‎‏‎‎‏‎"</string>
-    <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎Bluetooth‎‏‎‎‏‎"</string>
-    <string name="bluetooth_preference_paired_devices" msgid="5875643105380630583">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‎Paired devices‎‏‎‎‏‎"</string>
-    <string name="bluetooth_preference_found_devices" msgid="125155123214560511">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎Available devices‎‏‎‎‏‎"</string>
-    <string name="bluetooth_preference_no_paired_devices" msgid="483742146117390001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎No paired devices‎‏‎‎‏‎"</string>
-    <string name="bluetooth_preference_no_found_devices" msgid="1391812056491062262">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‎No available devices‎‏‎‎‏‎"</string>
-    <string name="bluetooth_preference_paired_dialog_title" msgid="2470829827455850904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎Paired device‎‏‎‎‏‎"</string>
-    <string name="bluetooth_preference_paired_dialog_name_label" msgid="3528740139365123415">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎Name‎‏‎‎‏‎"</string>
-    <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎Use for‎‏‎‎‏‎"</string>
-    <string name="wifi_ssid_hint" msgid="4155050863239489553">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‎Change the name of the Bluetooth device‎‏‎‎‏‎"</string>
-    <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎Bluetooth pairing request‎‏‎‎‏‎"</string>
-    <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‎Pair &amp; connect‎‏‎‎‏‎"</string>
-    <string name="bluetooth" msgid="5235115159234688629">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎Bluetooth‎‏‎‎‏‎"</string>
-    <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎Bluetooth pairing code‎‏‎‎‏‎"</string>
-    <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎PIN contains letters or symbols‎‏‎‎‏‎"</string>
-    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎Type the pairing code then press Return or Enter‎‏‎‎‏‎"</string>
-    <string name="bluetooth_pairing_request" msgid="4769675459526556801">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎Pair with ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
-    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎Allow ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to access your contacts and call history‎‏‎‎‏‎"</string>
-    <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‎You may also need to type this PIN on the other device.‎‏‎‎‏‎"</string>
-    <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎You may also need to type this passkey on the other device.‎‏‎‎‏‎"</string>
-    <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‏‎‎Must be 16 digits‎‏‎‎‏‎"</string>
-    <string name="bluetooth_pin_values_hint" msgid="1561325817559141687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‎‏‏‎‏‏‏‎Usually 0000 or 1234‎‏‎‎‏‎"</string>
-    <string name="bluetooth_notif_title" msgid="8374602799367803335">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎Pairing request‎‏‎‎‏‎"</string>
-    <string name="bluetooth_notif_message" msgid="1060821000510108726">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎Tap to pair with ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
+    <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎Bluetooth‎‏‎‎‏‎"</string>
+    <string name="bluetooth_quick_toggle_summary" msgid="4390699431893305353">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‎‏‎Turn on Bluetooth‎‏‎‎‏‎"</string>
+    <string name="bluetooth_settings" msgid="3878243366013638982">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎Bluetooth‎‏‎‎‏‎"</string>
+    <string name="bluetooth_disabled" msgid="4187409401590350572">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎Bluetooth disabled‎‏‎‎‏‎"</string>
+    <string name="bluetooth_settings_title" msgid="3794688574569688649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎‎‏‎‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‎Bluetooth‎‏‎‎‏‎"</string>
+    <string name="bluetooth_settings_summary" msgid="4023303473646769835">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‏‏‎Manage connections, set device name &amp; discoverability‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_computer" msgid="5223330129934365312">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎Computer‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_headset" msgid="6155254514321149935">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‏‎Headset‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_phone" msgid="8833977851215000426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎Phone‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‎Imaging‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_headphone" msgid="5362155791551671490">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‎Headphone‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="868933277567862622">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‎‎Input Peripheral‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎Bluetooth‎‏‎‎‏‎"</string>
+    <string name="bluetooth_preference_paired_devices" msgid="5875643105380630583">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‎Paired devices‎‏‎‎‏‎"</string>
+    <string name="bluetooth_preference_found_devices" msgid="125155123214560511">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎Available devices‎‏‎‎‏‎"</string>
+    <string name="bluetooth_preference_no_paired_devices" msgid="483742146117390001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎No paired devices‎‏‎‎‏‎"</string>
+    <string name="bluetooth_preference_no_found_devices" msgid="1391812056491062262">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‎No available devices‎‏‎‎‏‎"</string>
+    <string name="bluetooth_preference_paired_dialog_title" msgid="2470829827455850904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎Paired device‎‏‎‎‏‎"</string>
+    <string name="bluetooth_preference_paired_dialog_name_label" msgid="3528740139365123415">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎Name‎‏‎‎‏‎"</string>
+    <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎Use for‎‏‎‎‏‎"</string>
+    <string name="wifi_ssid_hint" msgid="4155050863239489553">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‎Change the name of the Bluetooth device‎‏‎‎‏‎"</string>
+    <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎Bluetooth pairing request‎‏‎‎‏‎"</string>
+    <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‎Pair &amp; connect‎‏‎‎‏‎"</string>
+    <string name="bluetooth" msgid="5235115159234688629">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎Bluetooth‎‏‎‎‏‎"</string>
+    <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎Bluetooth pairing code‎‏‎‎‏‎"</string>
+    <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎PIN contains letters or symbols‎‏‎‎‏‎"</string>
+    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎Type the pairing code then press Return or Enter‎‏‎‎‏‎"</string>
+    <string name="bluetooth_pairing_request" msgid="4769675459526556801">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎Pair with ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎Allow ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to access your contacts and call history‎‏‎‎‏‎"</string>
+    <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‎You may also need to type this PIN on the other device.‎‏‎‎‏‎"</string>
+    <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎You may also need to type this passkey on the other device.‎‏‎‎‏‎"</string>
+    <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‏‎‎Must be 16 digits‎‏‎‎‏‎"</string>
+    <string name="bluetooth_pin_values_hint" msgid="1561325817559141687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‎‏‏‎‏‏‏‎Usually 0000 or 1234‎‏‎‎‏‎"</string>
+    <string name="bluetooth_notif_title" msgid="8374602799367803335">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎Pairing request‎‏‎‎‏‎"</string>
+    <string name="bluetooth_notif_message" msgid="1060821000510108726">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎Tap to pair with ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎Languages‎‏‎‎‏‎"</string>
-    <string name="sound_settings" msgid="3072423952331872246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎Sound‎‏‎‎‏‎"</string>
-    <string name="ring_volume_title" msgid="3135241004980719442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎Ring volume‎‏‎‎‏‎"</string>
-    <string name="navi_volume_title" msgid="946292066759195165">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎Navigation volume‎‏‎‎‏‎"</string>
-    <string name="incoming_call_volume_title" msgid="6972117872424656876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎Ringtone‎‏‎‎‏‎"</string>
-    <string name="notification_volume_title" msgid="6749411263197157876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎Notification‎‏‎‎‏‎"</string>
-    <string name="media_volume_title" msgid="6697416686272606865">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‏‎Media‎‏‎‎‏‎"</string>
-    <string name="media_volume_summary" msgid="2961762827637127239">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎Set volume for music and videos‎‏‎‎‏‎"</string>
-    <string name="alarm_volume_title" msgid="840384014895796587">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎Alarm‎‏‎‎‏‎"</string>
-    <string name="applications_settings" msgid="794261395191035632">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‎App info‎‏‎‎‏‎"</string>
-    <string name="disable_text" msgid="4358165448648990820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎Disable‎‏‎‎‏‎"</string>
-    <string name="enable_text" msgid="1794971777861881238">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎Enable‎‏‎‎‏‎"</string>
-    <string name="permissions_label" msgid="2701446753515612685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‎‏‎Permissions‎‏‎‎‏‎"</string>
-    <string name="application_version_label" msgid="8556889839783311649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎‏‎Version: %1$s‎‏‎‎‏‎"</string>
-    <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎No permissions granted‎‏‎‎‏‎"</string>
-    <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎No permissions requested‎‏‎‎‏‎"</string>
-    <string name="data_usage_summary_title" msgid="4368024763485916986">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎Data usage‎‏‎‎‏‎"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎App data usage‎‏‎‎‏‎"</string>
-    <string name="force_stop" msgid="2153183697014720520">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎Force stop‎‏‎‎‏‎"</string>
-    <string name="computing_size" msgid="5791407621793083965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‎Computing…‎‏‎‎‏‎"</string>
+    <string name="sound_settings" msgid="3072423952331872246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎Sound‎‏‎‎‏‎"</string>
+    <string name="ring_volume_title" msgid="3135241004980719442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎Ring volume‎‏‎‎‏‎"</string>
+    <string name="navi_volume_title" msgid="946292066759195165">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎Navigation volume‎‏‎‎‏‎"</string>
+    <string name="incoming_call_volume_title" msgid="6972117872424656876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎Ringtone‎‏‎‎‏‎"</string>
+    <string name="notification_volume_title" msgid="6749411263197157876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎Notification‎‏‎‎‏‎"</string>
+    <string name="media_volume_title" msgid="6697416686272606865">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‏‎Media‎‏‎‎‏‎"</string>
+    <string name="media_volume_summary" msgid="2961762827637127239">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎Set volume for music and videos‎‏‎‎‏‎"</string>
+    <string name="alarm_volume_title" msgid="840384014895796587">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎Alarm‎‏‎‎‏‎"</string>
+    <string name="applications_settings" msgid="794261395191035632">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‎App info‎‏‎‎‏‎"</string>
+    <string name="disable_text" msgid="4358165448648990820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎Disable‎‏‎‎‏‎"</string>
+    <string name="enable_text" msgid="1794971777861881238">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎Enable‎‏‎‎‏‎"</string>
+    <string name="permissions_label" msgid="2701446753515612685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‎‏‎Permissions‎‏‎‎‏‎"</string>
+    <string name="application_version_label" msgid="8556889839783311649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎‏‎Version: %1$s‎‏‎‎‏‎"</string>
+    <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎No permissions granted‎‏‎‎‏‎"</string>
+    <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎No permissions requested‎‏‎‎‏‎"</string>
+    <string name="data_usage_summary_title" msgid="4368024763485916986">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎Data usage‎‏‎‎‏‎"</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎App data usage‎‏‎‎‏‎"</string>
+    <string name="force_stop" msgid="2153183697014720520">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎Force stop‎‏‎‎‏‎"</string>
+    <string name="computing_size" msgid="5791407621793083965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‎Computing…‎‏‎‎‏‎"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="COUNT_1">%d</xliff:g>‎‏‎‎‏‏‏‎ additional permissions‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="COUNT_0">%d</xliff:g>‎‏‎‎‏‏‏‎ additional permission‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="COUNT_1">%d</xliff:g>‎‏‎‎‏‏‏‎ additional permissions‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="COUNT_0">%d</xliff:g>‎‏‎‎‏‏‏‎ additional permission‎‏‎‎‏‎</item>
     </plurals>
-    <string name="system_setting_title" msgid="6864599341809463440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‎System‎‏‎‎‏‎"</string>
-    <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎System updates‎‏‎‎‏‎"</string>
+    <string name="system_setting_title" msgid="6864599341809463440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‎System‎‏‎‎‏‎"</string>
+    <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎System updates‎‏‎‎‏‎"</string>
     <string name="system_update_settings_list_item_summary" msgid="7395202602021608371"></string>
-    <string name="firmware_version" msgid="8491753744549309333">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎Android version‎‏‎‎‏‎"</string>
-    <string name="security_patch" msgid="4794276590178386903">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎Android security patch level‎‏‎‎‏‎"</string>
-    <string name="model_info" msgid="4966408071657934452">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎Model‎‏‎‎‏‎"</string>
-    <string name="baseband_version" msgid="2370088062235041897">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎Baseband version‎‏‎‎‏‎"</string>
-    <string name="kernel_version" msgid="7327212934187011508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‏‎‎‎Kernel version‎‏‎‎‏‎"</string>
-    <string name="build_number" msgid="3997326631001009102">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‏‎‎Build number‎‏‎‎‏‎"</string>
-    <string name="device_info_not_available" msgid="2095601973977376655">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‏‎Not available‎‏‎‎‏‎"</string>
-    <string name="device_status_activity_title" msgid="4083567497305368200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‎Status‎‏‎‎‏‎"</string>
-    <string name="device_status" msgid="267298179806290920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎Status‎‏‎‎‏‎"</string>
-    <string name="device_status_summary" product="tablet" msgid="600543254608862075">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎Status of the battery, network, and other information‎‏‎‎‏‎"</string>
-    <string name="device_status_summary" product="default" msgid="9130360324418117815">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‏‏‎Phone number, signal, etc.‎‏‎‎‏‎"</string>
-    <string name="about_settings" msgid="4329457966672592345">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎About‎‏‎‎‏‎"</string>
-    <string name="about_summary" msgid="5374623866267691206">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‎Android ‎‏‎‎‏‏‎<xliff:g id="VERSION">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="about_settings_summary" msgid="7975072809083281401">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎View legal info, status, software version‎‏‎‎‏‎"</string>
-    <string name="legal_information" msgid="1838443759229784762">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎Legal information‎‏‎‎‏‎"</string>
-    <string name="contributors_title" msgid="7698463793409916113">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎Contributors‎‏‎‎‏‎"</string>
-    <string name="manual" msgid="4819839169843240804">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎Manual‎‏‎‎‏‎"</string>
-    <string name="regulatory_labels" msgid="3165587388499646779">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎Regulatory labels‎‏‎‎‏‎"</string>
-    <string name="safety_and_regulatory_info" msgid="1204127697132067734">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎Safety &amp; regulatory manual‎‏‎‎‏‎"</string>
-    <string name="copyright_title" msgid="4220237202917417876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‎Copyright‎‏‎‎‏‎"</string>
-    <string name="license_title" msgid="936705938435249965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎License‎‏‎‎‏‎"</string>
-    <string name="terms_title" msgid="5201471373602628765">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‎Terms and conditions‎‏‎‎‏‎"</string>
-    <string name="webview_license_title" msgid="2531829466541104826">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎System WebView License‎‏‎‎‏‎"</string>
-    <string name="wallpaper_attributions" msgid="9201272150014500697">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎Wallpapers‎‏‎‎‏‎"</string>
-    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‎Satellite imagery providers:‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎©2014 CNES / Astrium, DigitalGlobe, Bluesky‎‏‎‎‏‎"</string>
-    <string name="settings_license_activity_title" msgid="8499293744313077709">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎Third-party licenses‎‏‎‎‏‎"</string>
-    <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‎There is a problem loading the licenses.‎‏‎‎‏‎"</string>
-    <string name="settings_license_activity_loading" msgid="6163263123009681841">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎Loading…‎‏‎‎‏‎"</string>
-    <string name="date_and_time_settings_title" msgid="4058492663544475485">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎Date &amp; time‎‏‎‎‏‎"</string>
-    <string name="date_and_time_settings_title_setup_wizard" msgid="7580119979694174107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎Set date and time‎‏‎‎‏‎"</string>
-    <string name="date_and_time_settings_summary" msgid="7669856855390804666">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‏‎‎Set date, time, time zone, &amp; formats‎‏‎‎‏‎"</string>
-    <string name="date_time_auto" msgid="3570339569471779767">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎Automatic date &amp; time‎‏‎‎‏‎"</string>
-    <string name="date_time_auto_summary" msgid="3311706425095342759">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎Use network-provided time‎‏‎‎‏‎"</string>
-    <string name="zone_auto" msgid="3701878581920206160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎‎Automatic time zone‎‏‎‎‏‎"</string>
-    <string name="zone_auto_summary" msgid="4345856882906981864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‎Use network-provided time zone‎‏‎‎‏‎"</string>
-    <string name="date_time_24hour_title" msgid="3025576547136168692">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‎24‑hour format‎‏‎‎‏‎"</string>
-    <string name="date_time_24hour" msgid="1137618702556486913">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‎‎‏‎Use 24-hour format‎‏‎‎‏‎"</string>
-    <string name="date_time_set_time_title" msgid="5884883050656937853">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎Time‎‏‎‎‏‎"</string>
-    <string name="date_time_set_time" msgid="6449555153906058248">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‎Set time‎‏‎‎‏‎"</string>
-    <string name="date_time_set_timezone_title" msgid="3001779256157093425">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎Time zone‎‏‎‎‏‎"</string>
-    <string name="date_time_set_timezone" msgid="4759353576185916944">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎Select time zone‎‏‎‎‏‎"</string>
-    <string name="date_time_set_date_title" msgid="6834785820357051138">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎Date‎‏‎‎‏‎"</string>
-    <string name="date_time_set_date" msgid="2537494485643283230">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎Set date‎‏‎‎‏‎"</string>
-    <string name="zone_list_menu_sort_alphabetically" msgid="7041628618528523514">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎Sort alphabetically‎‏‎‎‏‎"</string>
-    <string name="zone_list_menu_sort_by_timezone" msgid="4944880536057914136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‎‎‎Sort by time zone‎‏‎‎‏‎"</string>
-    <string name="date_picker_title" msgid="1533614225273770178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎Date‎‏‎‎‏‎"</string>
-    <string name="time_picker_title" msgid="7436045944320504639">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎Time‎‏‎‎‏‎"</string>
-    <string name="user_add_user_menu" msgid="5319151436895941496">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‎‎‎‎Add user‎‏‎‎‏‎"</string>
-    <string name="user_add_account_menu" msgid="6625351983590713721">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎‏‎Add account‎‏‎‎‏‎"</string>
-    <string name="user_delete_user_description" msgid="2300280525351142435">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‏‏‎Delete user‎‏‎‎‏‎"</string>
-    <string name="user_new_user_name" msgid="7115771396412339662">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‎‎New user‎‏‎‎‏‎"</string>
-    <string name="user_guest" msgid="3465399481257448601">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎Guest‎‏‎‎‏‎"</string>
-    <string name="user_admin" msgid="1535484812908584809">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎Admin‎‏‎‎‏‎"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎Signed in as admin‎‏‎‎‏‎"</string>
-    <string name="user_switch" msgid="6544839750534690781">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎Switch‎‏‎‎‏‎"</string>
-    <string name="current_user_name" msgid="3813671533249316823">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎You (%1$s)‎‏‎‎‏‎"</string>
-    <string name="user_name_label" msgid="3210832645046206845">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‏‎Name‎‏‎‎‏‎"</string>
-    <string name="user_summary_not_set_up" msgid="1473688119241224145">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎Not set up‎‏‎‎‏‎"</string>
-    <string name="edit_user_name_title" msgid="6890782937520262478">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‏‎‎Edit user name‎‏‎‎‏‎"</string>
-    <string name="users_list_title" msgid="770764290290240909">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎Users‎‏‎‎‏‎"</string>
-    <string name="accounts_settings_title" msgid="436190037084293471">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎Accounts‎‏‎‎‏‎"</string>
-    <string name="user_details_title" msgid="1104762783367701498">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‏‎‎User‎‏‎‎‏‎"</string>
-    <string name="no_accounts_added" msgid="5148163140691096055">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‏‎‏‏‏‎No accounts added‎‏‎‎‏‎"</string>
-    <string name="account_list_title" msgid="7631588514613843065">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎Accounts for ‎‏‎‎‏‏‎<xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="account_details_title" msgid="7529571432258448573">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎Account info‎‏‎‎‏‎"</string>
-    <string name="add_account_title" msgid="5988746086885210040">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎Add account‎‏‎‎‏‎"</string>
-    <string name="add_an_account" msgid="1072285034300995091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎Add an account‎‏‎‎‏‎"</string>
-    <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎Restricted profiles cannot add accounts‎‏‎‎‏‎"</string>
-    <string name="remove_account_title" msgid="8840386525787836381">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎Remove account‎‏‎‎‏‎"</string>
-    <string name="really_remove_account_title" msgid="3555164432587924900">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎Remove account?‎‏‎‎‏‎"</string>
-    <string name="really_remove_account_message" msgid="4296769280849579900">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎Removing this account will delete all of its messages, contacts, and other data from the device!‎‏‎‎‏‎"</string>
-    <string name="remove_account_failed" msgid="7472511529086294087">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‎This change isn\'t allowed by your admin‎‏‎‎‏‎"</string>
-    <string name="really_remove_user_title" msgid="4990029019291756762">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‎‎Remove this user?‎‏‎‎‏‎"</string>
-    <string name="really_remove_user_message" msgid="3828090902833944533">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎All apps and data will be deleted.‎‏‎‎‏‎"</string>
-    <string name="remove_user_error_title" msgid="2038275458657689420">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎Failed to remove user.‎‏‎‎‏‎"</string>
-    <string name="remove_user_error_message" msgid="6803947507134323358">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎Try again?‎‏‎‎‏‎"</string>
-    <string name="remove_user_error_dismiss" msgid="4006591159426844335">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‏‎Dismiss‎‏‎‎‏‎"</string>
-    <string name="remove_user_error_retry" msgid="8291692909396995093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎Retry‎‏‎‎‏‎"</string>
-    <string name="user_add_user_title" msgid="7458813670614932479">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎Add new user?‎‏‎‎‏‎"</string>
-    <string name="user_add_user_message_setup" msgid="6030901156040053106">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‎‎When you add a new user, that person needs to set up their space.‎‏‎‎‏‎"</string>
-    <string name="user_add_user_message_update" msgid="1528170913388932459">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎Any user can update apps for all other users.‎‏‎‎‏‎"</string>
-    <string name="security_settings_title" msgid="6955331714774709746">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎Security‎‏‎‎‏‎"</string>
-    <string name="security_settings_subtitle" msgid="2244635550239273229">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‏‎Screen lock‎‏‎‎‏‎"</string>
-    <string name="security_lock_none" msgid="1054645093754839638">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‎None‎‏‎‎‏‎"</string>
-    <string name="security_lock_pattern" msgid="1174352995619563104">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎Pattern‎‏‎‎‏‎"</string>
-    <string name="security_lock_pin" msgid="4891899974369503200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‎‎PIN‎‏‎‎‏‎"</string>
-    <string name="security_lock_password" msgid="4420203740048322494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎Password‎‏‎‎‏‎"</string>
-    <string name="lock_settings_picker_title" msgid="6590330165050361632">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎Choose a lock type‎‏‎‎‏‎"</string>
-    <string name="screen_lock_options" msgid="7023338635352915768">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎Screen lock options‎‏‎‎‏‎"</string>
-    <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎Enter your pattern‎‏‎‎‏‎"</string>
-    <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎Confirm‎‏‎‎‏‎"</string>
-    <string name="lockpattern_restart_button_text" msgid="9355771277617537">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎Redraw‎‏‎‎‏‎"</string>
-    <string name="continue_button_text" msgid="5129979170426836641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‎‏‎Continue‎‏‎‎‏‎"</string>
-    <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎‎Retry‎‏‎‎‏‎"</string>
-    <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‎Skip‎‏‎‎‏‎"</string>
-    <string name="set_screen_lock" msgid="5239317292691332780">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‎‏‏‎‎‎Set a screen lock‎‏‎‎‏‎"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎Choose your PIN‎‏‎‎‏‎"</string>
-    <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎Choose your pattern‎‏‎‎‏‎"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‎‏‏‎‏‎‎‎Choose your passowrd‎‏‎‎‏‎"</string>
-    <string name="current_screen_lock" msgid="637651611145979587">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎Current screen lock‎‏‎‎‏‎"</string>
-    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎For security, set a pattern‎‏‎‎‏‎"</string>
-    <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‎Clear‎‏‎‎‏‎"</string>
-    <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‎Cancel‎‏‎‎‏‎"</string>
-    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎Your new unlock pattern‎‏‎‎‏‎"</string>
-    <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‎Draw an unlock pattern‎‏‎‎‏‎"</string>
-    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‎Release finger when done‎‏‎‎‏‎"</string>
-    <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎Pattern recorded‎‏‎‎‏‎"</string>
-    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎Draw pattern again to confirm‎‏‎‎‏‎"</string>
-    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‎Connect at least 4 dots. Try again.‎‏‎‎‏‎"</string>
-    <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‎Wrong pattern‎‏‎‎‏‎"</string>
-    <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‎How to draw an unlock pattern‎‏‎‎‏‎"</string>
-    <string name="error_saving_lockpattern" msgid="2933512812768570130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‏‎‎Error saving pattern‎‏‎‎‏‎"</string>
-    <string name="okay" msgid="4589873324439764349">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎OK‎‏‎‎‏‎"</string>
-    <string name="remove_screen_lock_title" msgid="1234382338764193387">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎Remove screen lock?‎‏‎‎‏‎"</string>
-    <string name="remove_screen_lock_message" msgid="6675850371585564965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎This will allow anyone to access your account‎‏‎‎‏‎"</string>
-    <string name="lock_settings_enter_pin" msgid="1669172111244633904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎Enter your PIN‎‏‎‎‏‎"</string>
-    <string name="lock_settings_enter_password" msgid="2636669926649496367">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‎Enter your password‎‏‎‎‏‎"</string>
-    <string name="choose_lock_pin_message" msgid="2963792070267774417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎For security, set a PIN‎‏‎‎‏‎"</string>
-    <string name="confirm_your_pin_header" msgid="9096581288537156102">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‎‎Re-enter your PIN‎‏‎‎‏‎"</string>
-    <string name="choose_lock_pin_hints" msgid="7362906249992020844">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎PIN must be at least 4 digits‎‏‎‎‏‎"</string>
-    <string name="lockpin_invalid_pin" msgid="2149191577096327424">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎‎Pin invalid, must be at least 4 digits.‎‏‎‎‏‎"</string>
-    <string name="confirm_pins_dont_match" msgid="4607110139373520720">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‎PINs don\'t match‎‏‎‎‏‎"</string>
-    <string name="error_saving_lockpin" msgid="9011960139736000393">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‎Error saving PIN‎‏‎‎‏‎"</string>
-    <string name="lockscreen_wrong_pin" msgid="4922465731473805306">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎Wrong PIN‎‏‎‎‏‎"</string>
-    <string name="lockscreen_wrong_password" msgid="5757087577162231825">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎Wrong password‎‏‎‎‏‎"</string>
-    <string name="choose_lock_password_message" msgid="6124341145027370784">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎For security, set a password‎‏‎‎‏‎"</string>
-    <string name="confirm_your_password_header" msgid="7052891840366724938">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎Re-enter your password‎‏‎‎‏‎"</string>
-    <string name="confirm_passwords_dont_match" msgid="7300229965206501753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎Passwords don\'t match‎‏‎‎‏‎"</string>
-    <string name="lockpassword_clear_label" msgid="6363680971025188064">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎‎‎‎‎Clear‎‏‎‎‏‎"</string>
-    <string name="lockpassword_cancel_label" msgid="5791237697404166450">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‎‎Cancel‎‏‎‎‏‎"</string>
-    <string name="lockpassword_confirm_label" msgid="5918463281546146953">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎Confirm‎‏‎‎‏‎"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‎Must be at least 4 characters‎‏‎‎‏‎"</string>
-    <string name="lockpassword_password_too_short" msgid="6681218025001328405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‎‎‏‎‏‎‏‎Must be at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ characters‎‏‎‎‏‎"</string>
-    <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‏‏‎‏‎‎PIN must be at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ digits‎‏‎‎‏‎"</string>
-    <string name="lockpassword_password_too_long" msgid="7530214940279491291">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎Must be fewer than ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%d</xliff:g>‎‏‎‎‏‏‏‎ characters‎‏‎‎‏‎"</string>
-    <string name="lockpassword_pin_too_long" msgid="62957683396974404">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎Must be fewer than ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%d</xliff:g>‎‏‎‎‏‏‏‎ digits‎‏‎‎‏‎"</string>
-    <string name="lockpassword_pin_contains_non_digits" msgid="3044526271686839923">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎Must contain only digits 0-9.‎‏‎‎‏‎"</string>
-    <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎Device admin doesn\'t allow using a recent PIN‎‏‎‎‏‎"</string>
-    <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎Common PINs are blocked by your IT admin. Try a different PIN.‎‏‎‎‏‎"</string>
-    <string name="lockpassword_illegal_character" msgid="1984970060523635618">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎This can\'t include an invalid character.‎‏‎‎‏‎"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‏‏‏‎‎Password invalid, must be at least 4 characters.‎‏‎‎‏‎"</string>
+    <string name="firmware_version" msgid="8491753744549309333">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎Android version‎‏‎‎‏‎"</string>
+    <string name="security_patch" msgid="4794276590178386903">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎Android security patch level‎‏‎‎‏‎"</string>
+    <string name="model_info" msgid="4966408071657934452">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎Model‎‏‎‎‏‎"</string>
+    <string name="baseband_version" msgid="2370088062235041897">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎Baseband version‎‏‎‎‏‎"</string>
+    <string name="kernel_version" msgid="7327212934187011508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‏‎‎‎Kernel version‎‏‎‎‏‎"</string>
+    <string name="build_number" msgid="3997326631001009102">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‏‎‎Build number‎‏‎‎‏‎"</string>
+    <string name="device_info_not_available" msgid="2095601973977376655">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‏‎Not available‎‏‎‎‏‎"</string>
+    <string name="device_status_activity_title" msgid="4083567497305368200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‎Status‎‏‎‎‏‎"</string>
+    <string name="device_status" msgid="267298179806290920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎Status‎‏‎‎‏‎"</string>
+    <string name="device_status_summary" product="tablet" msgid="600543254608862075">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎Status of the battery, network, and other information‎‏‎‎‏‎"</string>
+    <string name="device_status_summary" product="default" msgid="9130360324418117815">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‏‏‎Phone number, signal, etc.‎‏‎‎‏‎"</string>
+    <string name="about_settings" msgid="4329457966672592345">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎About‎‏‎‎‏‎"</string>
+    <string name="about_summary" msgid="5374623866267691206">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‎Android ‎‏‎‎‏‏‎<xliff:g id="VERSION">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="about_settings_summary" msgid="7975072809083281401">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎View legal info, status, software version‎‏‎‎‏‎"</string>
+    <string name="legal_information" msgid="1838443759229784762">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎Legal information‎‏‎‎‏‎"</string>
+    <string name="contributors_title" msgid="7698463793409916113">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎Contributors‎‏‎‎‏‎"</string>
+    <string name="manual" msgid="4819839169843240804">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎Manual‎‏‎‎‏‎"</string>
+    <string name="regulatory_labels" msgid="3165587388499646779">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎Regulatory labels‎‏‎‎‏‎"</string>
+    <string name="safety_and_regulatory_info" msgid="1204127697132067734">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎Safety &amp; regulatory manual‎‏‎‎‏‎"</string>
+    <string name="copyright_title" msgid="4220237202917417876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‎Copyright‎‏‎‎‏‎"</string>
+    <string name="license_title" msgid="936705938435249965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎License‎‏‎‎‏‎"</string>
+    <string name="terms_title" msgid="5201471373602628765">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‎Terms and conditions‎‏‎‎‏‎"</string>
+    <string name="webview_license_title" msgid="2531829466541104826">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎System WebView License‎‏‎‎‏‎"</string>
+    <string name="wallpaper_attributions" msgid="9201272150014500697">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎Wallpapers‎‏‎‎‏‎"</string>
+    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‎Satellite imagery providers:‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎©2014 CNES / Astrium, DigitalGlobe, Bluesky‎‏‎‎‏‎"</string>
+    <string name="settings_license_activity_title" msgid="8499293744313077709">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎Third-party licenses‎‏‎‎‏‎"</string>
+    <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‎There is a problem loading the licenses.‎‏‎‎‏‎"</string>
+    <string name="settings_license_activity_loading" msgid="6163263123009681841">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎Loading…‎‏‎‎‏‎"</string>
+    <string name="date_and_time_settings_title" msgid="4058492663544475485">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎Date &amp; time‎‏‎‎‏‎"</string>
+    <string name="date_and_time_settings_title_setup_wizard" msgid="7580119979694174107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎Set date and time‎‏‎‎‏‎"</string>
+    <string name="date_and_time_settings_summary" msgid="7669856855390804666">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‏‎‎Set date, time, time zone, &amp; formats‎‏‎‎‏‎"</string>
+    <string name="date_time_auto" msgid="3570339569471779767">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎Automatic date &amp; time‎‏‎‎‏‎"</string>
+    <string name="date_time_auto_summary" msgid="3311706425095342759">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎Use network-provided time‎‏‎‎‏‎"</string>
+    <string name="zone_auto" msgid="3701878581920206160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎‎Automatic time zone‎‏‎‎‏‎"</string>
+    <string name="zone_auto_summary" msgid="4345856882906981864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‎Use network-provided time zone‎‏‎‎‏‎"</string>
+    <string name="date_time_24hour_title" msgid="3025576547136168692">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‎24‑hour format‎‏‎‎‏‎"</string>
+    <string name="date_time_24hour" msgid="1137618702556486913">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‎‎‏‎Use 24-hour format‎‏‎‎‏‎"</string>
+    <string name="date_time_set_time_title" msgid="5884883050656937853">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎Time‎‏‎‎‏‎"</string>
+    <string name="date_time_set_time" msgid="6449555153906058248">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‎Set time‎‏‎‎‏‎"</string>
+    <string name="date_time_set_timezone_title" msgid="3001779256157093425">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎Time zone‎‏‎‎‏‎"</string>
+    <string name="date_time_set_timezone" msgid="4759353576185916944">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎Select time zone‎‏‎‎‏‎"</string>
+    <string name="date_time_set_date_title" msgid="6834785820357051138">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎Date‎‏‎‎‏‎"</string>
+    <string name="date_time_set_date" msgid="2537494485643283230">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎Set date‎‏‎‎‏‎"</string>
+    <string name="zone_list_menu_sort_alphabetically" msgid="7041628618528523514">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎Sort alphabetically‎‏‎‎‏‎"</string>
+    <string name="zone_list_menu_sort_by_timezone" msgid="4944880536057914136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‎‎‎Sort by time zone‎‏‎‎‏‎"</string>
+    <string name="date_picker_title" msgid="1533614225273770178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎Date‎‏‎‎‏‎"</string>
+    <string name="time_picker_title" msgid="7436045944320504639">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎Time‎‏‎‎‏‎"</string>
+    <string name="user_add_user_menu" msgid="5319151436895941496">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‎‎‎‎Add user‎‏‎‎‏‎"</string>
+    <string name="user_add_account_menu" msgid="6625351983590713721">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎‏‎Add account‎‏‎‎‏‎"</string>
+    <string name="user_delete_user_description" msgid="2300280525351142435">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‏‏‎Delete user‎‏‎‎‏‎"</string>
+    <string name="user_new_user_name" msgid="7115771396412339662">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‎‎New user‎‏‎‎‏‎"</string>
+    <string name="user_guest" msgid="3465399481257448601">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎Guest‎‏‎‎‏‎"</string>
+    <string name="user_switch" msgid="6544839750534690781">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎Switch‎‏‎‎‏‎"</string>
+    <string name="current_user_name" msgid="3813671533249316823">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎You (%1$s)‎‏‎‎‏‎"</string>
+    <string name="user_name_label" msgid="3210832645046206845">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‏‎Name‎‏‎‎‏‎"</string>
+    <string name="user_summary_not_set_up" msgid="1473688119241224145">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎Not set up‎‏‎‎‏‎"</string>
+    <string name="edit_user_name_title" msgid="6890782937520262478">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‏‎‎Edit user name‎‏‎‎‏‎"</string>
+    <string name="users_list_title" msgid="770764290290240909">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎Users‎‏‎‎‏‎"</string>
+    <string name="accounts_settings_title" msgid="436190037084293471">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎Accounts‎‏‎‎‏‎"</string>
+    <string name="user_details_title" msgid="1104762783367701498">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‏‎‎User‎‏‎‎‏‎"</string>
+    <string name="no_accounts_added" msgid="5148163140691096055">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‏‎‏‏‏‎No accounts added‎‏‎‎‏‎"</string>
+    <string name="account_list_title" msgid="7631588514613843065">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎Accounts for ‎‏‎‎‏‏‎<xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="account_details_title" msgid="7529571432258448573">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎Account info‎‏‎‎‏‎"</string>
+    <string name="add_account_title" msgid="5988746086885210040">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎Add account‎‏‎‎‏‎"</string>
+    <string name="add_an_account" msgid="1072285034300995091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎Add an account‎‏‎‎‏‎"</string>
+    <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎Restricted profiles cannot add accounts‎‏‎‎‏‎"</string>
+    <string name="remove_account_title" msgid="8840386525787836381">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎Remove account‎‏‎‎‏‎"</string>
+    <string name="really_remove_account_title" msgid="3555164432587924900">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎Remove account?‎‏‎‎‏‎"</string>
+    <string name="really_remove_account_message" msgid="4296769280849579900">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎Removing this account will delete all of its messages, contacts, and other data from the device!‎‏‎‎‏‎"</string>
+    <string name="remove_account_failed" msgid="7472511529086294087">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‎This change isn\'t allowed by your admin‎‏‎‎‏‎"</string>
+    <string name="really_remove_user_title" msgid="4990029019291756762">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‎‎Remove this user?‎‏‎‎‏‎"</string>
+    <string name="really_remove_user_message" msgid="3828090902833944533">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎All apps and data will be deleted.‎‏‎‎‏‎"</string>
+    <string name="remove_user_error_title" msgid="2038275458657689420">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎Failed to remove user.‎‏‎‎‏‎"</string>
+    <string name="remove_user_error_message" msgid="6803947507134323358">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎Try again?‎‏‎‎‏‎"</string>
+    <string name="remove_user_error_dismiss" msgid="4006591159426844335">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‏‎Dismiss‎‏‎‎‏‎"</string>
+    <string name="remove_user_error_retry" msgid="8291692909396995093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎Retry‎‏‎‎‏‎"</string>
+    <string name="user_add_user_title" msgid="7458813670614932479">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎Add new user?‎‏‎‎‏‎"</string>
+    <string name="user_add_user_message_setup" msgid="6030901156040053106">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‎‎When you add a new user, that person needs to set up their space.‎‏‎‎‏‎"</string>
+    <string name="user_add_user_message_update" msgid="1528170913388932459">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎Any user can update apps for all other users.‎‏‎‎‏‎"</string>
+    <string name="security_settings_title" msgid="6955331714774709746">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎Security‎‏‎‎‏‎"</string>
+    <string name="security_settings_subtitle" msgid="2244635550239273229">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‏‎Screen lock‎‏‎‎‏‎"</string>
+    <string name="security_lock_none" msgid="1054645093754839638">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‎None‎‏‎‎‏‎"</string>
+    <string name="security_lock_pattern" msgid="1174352995619563104">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎Pattern‎‏‎‎‏‎"</string>
+    <string name="security_lock_pin" msgid="4891899974369503200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‎‎PIN‎‏‎‎‏‎"</string>
+    <string name="security_lock_password" msgid="4420203740048322494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎Password‎‏‎‎‏‎"</string>
+    <string name="lock_settings_picker_title" msgid="6590330165050361632">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎Choose a lock type‎‏‎‎‏‎"</string>
+    <string name="screen_lock_options" msgid="7023338635352915768">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎Screen lock options‎‏‎‎‏‎"</string>
+    <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎Enter your pattern‎‏‎‎‏‎"</string>
+    <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎Confirm‎‏‎‎‏‎"</string>
+    <string name="lockpattern_restart_button_text" msgid="9355771277617537">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎Redraw‎‏‎‎‏‎"</string>
+    <string name="continue_button_text" msgid="5129979170426836641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‎‏‎Continue‎‏‎‎‏‎"</string>
+    <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎‎Retry‎‏‎‎‏‎"</string>
+    <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‎Skip‎‏‎‎‏‎"</string>
+    <string name="set_screen_lock" msgid="5239317292691332780">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‎‏‏‎‎‎Set a screen lock‎‏‎‎‏‎"</string>
+    <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎Choose your pattern‎‏‎‎‏‎"</string>
+    <string name="current_screen_lock" msgid="637651611145979587">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎Current screen lock‎‏‎‎‏‎"</string>
+    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎For security, set a pattern‎‏‎‎‏‎"</string>
+    <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‎Clear‎‏‎‎‏‎"</string>
+    <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‎Cancel‎‏‎‎‏‎"</string>
+    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎Your new unlock pattern‎‏‎‎‏‎"</string>
+    <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‎Draw an unlock pattern‎‏‎‎‏‎"</string>
+    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‎Release finger when done‎‏‎‎‏‎"</string>
+    <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎Pattern recorded‎‏‎‎‏‎"</string>
+    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎Draw pattern again to confirm‎‏‎‎‏‎"</string>
+    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‎Connect at least 4 dots. Try again.‎‏‎‎‏‎"</string>
+    <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‎Wrong pattern‎‏‎‎‏‎"</string>
+    <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‎How to draw an unlock pattern‎‏‎‎‏‎"</string>
+    <string name="error_saving_lockpattern" msgid="2933512812768570130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‏‎‎Error saving pattern‎‏‎‎‏‎"</string>
+    <string name="okay" msgid="4589873324439764349">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎OK‎‏‎‎‏‎"</string>
+    <string name="remove_screen_lock_title" msgid="1234382338764193387">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎Remove screen lock?‎‏‎‎‏‎"</string>
+    <string name="remove_screen_lock_message" msgid="6675850371585564965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎This will allow anyone to access your account‎‏‎‎‏‎"</string>
+    <string name="lock_settings_enter_pin" msgid="1669172111244633904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎Enter your PIN‎‏‎‎‏‎"</string>
+    <string name="lock_settings_enter_password" msgid="2636669926649496367">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‎Enter your password‎‏‎‎‏‎"</string>
+    <string name="choose_lock_pin_message" msgid="2963792070267774417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎For security, set a PIN‎‏‎‎‏‎"</string>
+    <string name="confirm_your_pin_header" msgid="9096581288537156102">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‎‎Re-enter your PIN‎‏‎‎‏‎"</string>
+    <string name="choose_lock_pin_hints" msgid="7362906249992020844">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎PIN must be at least 4 digits‎‏‎‎‏‎"</string>
+    <string name="lockpin_invalid_pin" msgid="2149191577096327424">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎‎Pin invalid, must be at least 4 digits.‎‏‎‎‏‎"</string>
+    <string name="confirm_pins_dont_match" msgid="4607110139373520720">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‎PINs don\'t match‎‏‎‎‏‎"</string>
+    <string name="error_saving_lockpin" msgid="9011960139736000393">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‎Error saving PIN‎‏‎‎‏‎"</string>
+    <string name="lockscreen_wrong_pin" msgid="4922465731473805306">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎Wrong PIN‎‏‎‎‏‎"</string>
+    <string name="lockscreen_wrong_password" msgid="5757087577162231825">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎Wrong password‎‏‎‎‏‎"</string>
+    <string name="choose_lock_password_message" msgid="6124341145027370784">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎For security, set a password‎‏‎‎‏‎"</string>
+    <string name="confirm_your_password_header" msgid="7052891840366724938">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎Re-enter your password‎‏‎‎‏‎"</string>
+    <string name="confirm_passwords_dont_match" msgid="7300229965206501753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎Passwords don\'t match‎‏‎‎‏‎"</string>
+    <string name="lockpassword_clear_label" msgid="6363680971025188064">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎‎‎‎‎Clear‎‏‎‎‏‎"</string>
+    <string name="lockpassword_cancel_label" msgid="5791237697404166450">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‎‎Cancel‎‏‎‎‏‎"</string>
+    <string name="lockpassword_confirm_label" msgid="5918463281546146953">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎Confirm‎‏‎‎‏‎"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎Password must be between 4-8 characters with at least 1 number‎‏‎‎‏‎"</string>
+    <string name="lockpassword_password_too_short" msgid="6681218025001328405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‎‎‏‎‏‎‏‎Must be at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ characters‎‏‎‎‏‎"</string>
+    <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‏‏‎‏‎‎PIN must be at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ digits‎‏‎‎‏‎"</string>
+    <string name="lockpassword_password_too_long" msgid="7530214940279491291">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎Must be fewer than ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%d</xliff:g>‎‏‎‎‏‏‏‎ characters‎‏‎‎‏‎"</string>
+    <string name="lockpassword_pin_too_long" msgid="62957683396974404">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎Must be fewer than ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%d</xliff:g>‎‏‎‎‏‏‏‎ digits‎‏‎‎‏‎"</string>
+    <string name="lockpassword_pin_contains_non_digits" msgid="3044526271686839923">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎Must contain only digits 0-9.‎‏‎‎‏‎"</string>
+    <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎Device admin doesn\'t allow using a recent PIN‎‏‎‎‏‎"</string>
+    <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎Common PINs are blocked by your IT admin. Try a different PIN.‎‏‎‎‏‎"</string>
+    <string name="lockpassword_illegal_character" msgid="1984970060523635618">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎This can\'t include an invalid character.‎‏‎‎‏‎"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎Password invalid, must be 4-8 characters, contain at least 1 digit, 1 letter, no whitespace.‎‏‎‎‏‎"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ letters‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‎Must contain at least 1 letter‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ letters‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‎Must contain at least 1 letter‎‏‎‎‏‎</item>
     </plurals>
     <plurals name="lockpassword_password_requires_lowercase" formatted="false" msgid="2267487180744744833">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ lowercase letters‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎Must contain at least 1 lowercase letter‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ lowercase letters‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎Must contain at least 1 lowercase letter‎‏‎‎‏‎</item>
     </plurals>
     <plurals name="lockpassword_password_requires_uppercase" formatted="false" msgid="7999264563026517898">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ uppercase letters‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎Must contain at least 1 uppercase letter‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ uppercase letters‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎Must contain at least 1 uppercase letter‎‏‎‎‏‎</item>
     </plurals>
     <plurals name="lockpassword_password_requires_numeric" formatted="false" msgid="7935079851855168646">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ numerical digits‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎Must contain at least 1 numerical digit‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ numerical digits‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎Must contain at least 1 numerical digit‎‏‎‎‏‎</item>
     </plurals>
     <plurals name="lockpassword_password_requires_symbols" formatted="false" msgid="3994046435150094132">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ special symbols‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎Must contain at least 1 special symbol‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ special symbols‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎Must contain at least 1 special symbol‎‏‎‎‏‎</item>
     </plurals>
     <plurals name="lockpassword_password_requires_nonletter" formatted="false" msgid="6878486326748506524">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ non-letter characters‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‎‎Must contain at least 1 non-letter character‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‎‎Must contain at least ‎‏‎‎‏‏‎<xliff:g id="COUNT">%d</xliff:g>‎‏‎‎‏‏‏‎ non-letter characters‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‎‎Must contain at least 1 non-letter character‎‏‎‎‏‎</item>
     </plurals>
-    <string name="lockpassword_password_recently_used" msgid="8255729487108602924">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎Device admin doesn\'t allow using a recent password‎‏‎‎‏‎"</string>
-    <string name="error_saving_password" msgid="8334882262622500658">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‎Error saving password‎‏‎‎‏‎"</string>
-    <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‏‏‎Common passwords are blocked by your IT admin. Try a different password.‎‏‎‎‏‎"</string>
-    <string name="lockpassword_pin_no_sequential_digits" msgid="38813552228809240">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎Ascending, descending, or repeated sequence of digits isn\'t allowed.‎‏‎‎‏‎"</string>
-    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎Screen lock options‎‏‎‎‏‎"</string>
-    <string name="forget" msgid="3971143908183848527">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‏‎‎‏‏‏‏‎Forget‎‏‎‎‏‎"</string>
-    <string name="delete_button" msgid="5840500432614610850">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‎Delete‎‏‎‎‏‎"</string>
-    <string name="remove_button" msgid="6664656962868194178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎Remove‎‏‎‎‏‎"</string>
-    <string name="cancel" msgid="750286395700355455">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‏‏‎Cancel‎‏‎‎‏‎"</string>
-    <string name="backspace_key" msgid="1545590866688979099">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎Backspace key‎‏‎‎‏‎"</string>
-    <string name="enter_key" msgid="2121394305541579468">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎Enter key‎‏‎‎‏‎"</string>
-    <string name="exit_retail_button_text" msgid="6093240315583384473">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‎Exit Demo‎‏‎‎‏‎"</string>
-    <string name="exit_retail_mode_dialog_title" msgid="7970631760237469168">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‏‎‎‎‎‎Exit demo mode‎‏‎‎‏‎"</string>
-    <string name="exit_retail_mode_dialog_body" msgid="8314316171782527301">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎This will delete the demo account and factory data reset the system. All user data will be lost.‎‏‎‎‏‎"</string>
-    <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‎Exit Demo‎‏‎‎‏‎"</string>
-    <string name="suggestion_primary_button" msgid="6421115494714083020">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎finish setup‎‏‎‎‏‎"</string>
-    <string name="suggestion_secondary_button" msgid="7075088546904464681">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎not now‎‏‎‎‏‎"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎Feature not available while driving.‎‏‎‎‏‎"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‎Can\'t add user while driving.‎‏‎‎‏‎"</string>
+    <string name="lockpassword_password_recently_used" msgid="8255729487108602924">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎Device admin doesn\'t allow using a recent password‎‏‎‎‏‎"</string>
+    <string name="error_saving_password" msgid="8334882262622500658">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‎Error saving password‎‏‎‎‏‎"</string>
+    <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‏‏‎Common passwords are blocked by your IT admin. Try a different password.‎‏‎‎‏‎"</string>
+    <string name="lockpassword_pin_no_sequential_digits" msgid="38813552228809240">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎Ascending, descending, or repeated sequence of digits isn\'t allowed.‎‏‎‎‏‎"</string>
+    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎Screen lock options‎‏‎‎‏‎"</string>
+    <string name="forget" msgid="3971143908183848527">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‏‎‎‏‏‏‏‎Forget‎‏‎‎‏‎"</string>
+    <string name="delete_button" msgid="5840500432614610850">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‎Delete‎‏‎‎‏‎"</string>
+    <string name="remove_button" msgid="6664656962868194178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎Remove‎‏‎‎‏‎"</string>
+    <string name="cancel" msgid="750286395700355455">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‏‏‎Cancel‎‏‎‎‏‎"</string>
+    <string name="backspace_key" msgid="1545590866688979099">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‎Backspace key‎‏‎‎‏‎"</string>
+    <string name="enter_key" msgid="2121394305541579468">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎Enter key‎‏‎‎‏‎"</string>
+    <string name="exit_retail_button_text" msgid="6093240315583384473">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‎Exit Demo‎‏‎‎‏‎"</string>
+    <string name="exit_retail_mode_dialog_title" msgid="7970631760237469168">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‏‎‎‎‎‎Exit demo mode‎‏‎‎‏‎"</string>
+    <string name="exit_retail_mode_dialog_body" msgid="8314316171782527301">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎This will delete the demo account and factory data reset the system. All user data will be lost.‎‏‎‎‏‎"</string>
+    <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‎Exit Demo‎‏‎‎‏‎"</string>
+    <string name="suggestion_primary_button" msgid="6421115494714083020">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎finish setup‎‏‎‎‏‎"</string>
+    <string name="suggestion_secondary_button" msgid="7075088546904464681">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎not now‎‏‎‎‏‎"</string>
+    <string name="restricted_while_driving" msgid="6217369093121968299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎Feature not available while driving.‎‏‎‎‏‎"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 21d9cd6..184607e 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Sincronizando solicitud"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Presiona para sincronizar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Idiomas"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Sonido"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volumen de tono"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volumen de navegación"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Borrar usuario"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Usuario nuevo"</string>
     <string name="user_guest" msgid="3465399481257448601">"Invitado"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrador"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Accediste como administrador"</string>
     <string name="user_switch" msgid="6544839750534690781">"Cambiar"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Tú (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nombre"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Reintentar"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Omitir"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Establece un bloqueo de pantalla"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Elige un PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Elige un patrón"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Elige una contraseña"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Bloqueo de pantalla actual"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Por seguridad, establece un patrón"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Borrar"</string>
@@ -224,7 +219,7 @@
     <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"Tu nuevo patrón de desbloqueo"</string>
     <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"Dibuja un patrón de desbloqueo"</string>
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Levanta el dedo cuando termines"</string>
-    <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"Se registró el patrón"</string>
+    <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"Se grabó el patrón"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"Dibuja el patrón de nuevo para confirmar"</string>
     <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Conecta al menos 4 puntos. Reinténtalo."</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"Patrón incorrecto"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Borrar"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancelar"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirmar"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Debe tener al menos 4 caracteres"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Contraseña: 4-8 caracteres (1 número)"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Debe tener al menos <xliff:g id="COUNT">%d</xliff:g> caracteres"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"El PIN debe tener al menos <xliff:g id="COUNT">%d</xliff:g> dígitos"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Debe tener menos de <xliff:g id="NUMBER">%d</xliff:g> caracteres"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"El administrador del dispositivo no permite el uso de PIN recientes"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Tu administrador de TI bloquea los PIN comunes. Prueba con otro."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"No puede incluir un carácter no válido."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"La contraseña no es válida, ya que debe tener al menos 4 caracteres."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"La contraseña no es válida (debe tener entre 4 y 8 caracteres, y contener al menos 1 dígito, 1 letra y ningún espacio en blanco)."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Debe tener al menos <xliff:g id="COUNT">%d</xliff:g> letras</item>
       <item quantity="one">Debe tener al menos 1 letra</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"terminar de configurar"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ahora no"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Esta función no está disponible mientras conduces."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"No puedes agregar usuarios mientras conduces."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 0a18a32..66fdacc 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Solicitud de vinculación"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Toca para vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Idiomas"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Sonido"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volumen del tono"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volumen de navegación"</string>
@@ -142,7 +141,7 @@
     <string name="terms_title" msgid="5201471373602628765">"Términos y condiciones"</string>
     <string name="webview_license_title" msgid="2531829466541104826">"Licencia de WebView del sistema"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"Fondos de pantalla"</string>
-    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Proveedores de imágenes de satélite:\n© 2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
+    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Proveedores de imágenes de satélite:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
     <string name="settings_license_activity_title" msgid="8499293744313077709">"Licencias de terceros"</string>
     <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"No se han podido cargar las licencias."</string>
     <string name="settings_license_activity_loading" msgid="6163263123009681841">"Cargando…"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Eliminar usuario"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nuevo usuario"</string>
     <string name="user_guest" msgid="3465399481257448601">"Invitado"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrador"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Sesión iniciada como administrator"</string>
     <string name="user_switch" msgid="6544839750534690781">"Cambiar"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Tú (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nombre"</string>
@@ -194,7 +191,7 @@
     <string name="really_remove_user_message" msgid="3828090902833944533">"Se eliminarán los datos y las aplicaciones."</string>
     <string name="remove_user_error_title" msgid="2038275458657689420">"No se ha podido quitar el usuario."</string>
     <string name="remove_user_error_message" msgid="6803947507134323358">"¿Reintentar?"</string>
-    <string name="remove_user_error_dismiss" msgid="4006591159426844335">"Cerrar"</string>
+    <string name="remove_user_error_dismiss" msgid="4006591159426844335">"Ignorar"</string>
     <string name="remove_user_error_retry" msgid="8291692909396995093">"Reintentar"</string>
     <string name="user_add_user_title" msgid="7458813670614932479">"¿Añadir nuevo usuario?"</string>
     <string name="user_add_user_message_setup" msgid="6030901156040053106">"Al añadir un nuevo usuario, este debe configurar su espacio."</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Reintentar"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Saltar"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Definir un bloqueo de pantalla"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Elige un PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Elige un patrón"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Elige una contraseña"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Bloqueo de pantalla actual"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Por seguridad, establece un patrón"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Borrar"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Borrar"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancelar"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirmar"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Debe tener al menos 4 caracteres"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Contraseña entre 4 y 8 caracteres, mínimo 1 número"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Debe tener al menos <xliff:g id="COUNT">%d</xliff:g> caracteres"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"El PIN debe tener al menos <xliff:g id="COUNT">%d</xliff:g> dígitos"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Debe tener menos de <xliff:g id="NUMBER">%d</xliff:g> caracteres"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"El administrador de dispositivos no permite utilizar un PIN reciente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"El administrador de TI bloquea los PIN comunes. Prueba a utilizar otro PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"No puede incluir un carácter que no sea válido."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"La contraseña no es válida porque debe tener al menos 4 caracteres."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"La contraseña no es válida: debe tener entre 4 y 8 caracteres, incluir al menos un dígito y una letra, y no tener espacios en blanco."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Debe tener al menos <xliff:g id="COUNT">%d</xliff:g> letras</item>
       <item quantity="one">Debe tener al menos 1 letra</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"finalizar configuración"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ahora no"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Esta función no está disponible mientras conduces."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"No puedes añadir usuarios mientras conduces."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 00d4b77..98f6395 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Sidumistaotlus"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Puudutage seadmega <xliff:g id="DEVICE_NAME">%1$s</xliff:g> sidumiseks."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Keeled"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Heli"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Helina helitugevus"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigatsiooni helitugevus"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Kasutaja kustutamine"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Uus kasutaja"</string>
     <string name="user_guest" msgid="3465399481257448601">"Külaline"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administraator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Sisse logitud administraatorina"</string>
     <string name="user_switch" msgid="6544839750534690781">"Lüliti"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Teie (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nimi"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Proovi uuesti"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Jäta vahele"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ekraaniluku seadistamine"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Valige PIN-kood"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Mustri valimine"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Valige parool"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Praegune ekraanilukk"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Turvalisuse huvides määrake muster"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Kustuta"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Kustuta"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Tühista"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Kinnita"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Peab olema vähemalt neli tähemärki"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Parool peab olema vahemikus 4–8 tähemärki ja sisaldama vähemalt 1 numbrit"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Peab sisaldama vähemalt <xliff:g id="COUNT">%d</xliff:g> tähemärki"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN-kood peab sisald. vähemalt <xliff:g id="COUNT">%d</xliff:g> numbrit"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Peab olema lühem kui <xliff:g id="NUMBER">%d</xliff:g> tähemärki"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Seadme administraator ei luba kasutada viimast PIN-koodi"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"IT-administraator on levinud PIN-koodid blokeerinud. Proovige muud PIN-koodi."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"See ei tohi sisaldada sobimatut tähemärki."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Parool on sobimatu, peab olema vähemalt neli tähemärki."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Parool on sobimatu. See peab koosnema 4–8 tähemärgist, sisaldama vähemalt ühte numbrit, ühte tähte ega tohi sisaldada tühikuid."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Peab sisaldama vähemalt <xliff:g id="COUNT">%d</xliff:g> tähte</item>
       <item quantity="one">Peab sisaldama vähemalt 1 tähte</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"vii seadistus lõpule"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"mitte praegu"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funktsioon pole sõidu ajal saadaval."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Sõidu ajal ei saa kasutajat lisada."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 72b5616..f373b58 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -30,7 +30,7 @@
     <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"ilundu pantaila, ukipen-pantaila, bateria"</string>
     <string name="keywords_display_night_display" msgid="2922294576679769957">"pantaila iluna, gaua, tonalitatea"</string>
     <string name="night_mode_tile_label" msgid="6603597795502131664">"Gau modua"</string>
-    <string name="wifi_settings" msgid="7701477685273103841">"Wifia"</string>
+    <string name="wifi_settings" msgid="7701477685273103841">"Wi-Fi"</string>
     <string name="wifi_settings_summary" msgid="6095898149997291025">"Konfiguratu eta kudeatu hari gabeko sarbide-puntuak"</string>
     <string name="wifi_starting" msgid="473253087503153167">"Wi-Fi konexioa aktibatzen…"</string>
     <string name="wifi_stopping" msgid="3534173972547890148">"Wi-Fi konexioa desaktibatzen…"</string>
@@ -45,7 +45,7 @@
     <string name="wifi_security" msgid="158358046038876532">"Segurtasuna"</string>
     <string name="wifi_signal" msgid="1817579728350364549">"Seinalearen indarra"</string>
     <string name="wifi_status" msgid="5688013206066543952">"Egoera"</string>
-    <string name="wifi_speed" msgid="1650692446731850781">"Lotura-abiadura"</string>
+    <string name="wifi_speed" msgid="1650692446731850781">"Esteken abiadura"</string>
     <string name="wifi_frequency" msgid="8951455949682864922">"Maiztasuna"</string>
     <string name="wifi_ip_address" msgid="3128140627890954061">"IP helbidea"</string>
     <string name="access_point_tag_key" msgid="1517143378973053337">"access_point_tag_key"</string>
@@ -55,11 +55,11 @@
     <item msgid="3859756017461098953">"Ona"</item>
     <item msgid="1521103743353335724">"Bikaina"</item>
   </string-array>
-    <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"Bluetooth-a"</string>
+    <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"Bluetooth konexioa"</string>
     <string name="bluetooth_quick_toggle_summary" msgid="4390699431893305353">"Aktibatu Bluetooth konexioa"</string>
-    <string name="bluetooth_settings" msgid="3878243366013638982">"Bluetooth-a"</string>
+    <string name="bluetooth_settings" msgid="3878243366013638982">"Bluetooth konexioa"</string>
     <string name="bluetooth_disabled" msgid="4187409401590350572">"Bluetooth konexioa desgaituta dago"</string>
-    <string name="bluetooth_settings_title" msgid="3794688574569688649">"Bluetooth-a"</string>
+    <string name="bluetooth_settings_title" msgid="3794688574569688649">"Bluetooth konexioa"</string>
     <string name="bluetooth_settings_summary" msgid="4023303473646769835">"Kudeatu konexioak, eta ezarri gailuaren izena eta ikusgaitasuna"</string>
     <string name="bluetooth_talkback_computer" msgid="5223330129934365312">"Ordenagailua"</string>
     <string name="bluetooth_talkback_headset" msgid="6155254514321149935">"Entzungailua"</string>
@@ -67,7 +67,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"Irudietarako gailua"</string>
     <string name="bluetooth_talkback_headphone" msgid="5362155791551671490">"Aurikularra"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="868933277567862622">"Idazteko gailua"</string>
-    <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"Bluetooth-a"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"Bluetooth konexioa"</string>
     <string name="bluetooth_preference_paired_devices" msgid="5875643105380630583">"Parekatutako gailuak"</string>
     <string name="bluetooth_preference_found_devices" msgid="125155123214560511">"Gailu erabilgarriak"</string>
     <string name="bluetooth_preference_no_paired_devices" msgid="483742146117390001">"Ez dago parekatutako gailurik"</string>
@@ -78,12 +78,12 @@
     <string name="wifi_ssid_hint" msgid="4155050863239489553">"Aldatu Bluetooth gailuaren izena"</string>
     <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"Bluetooth bidez parekatzeko eskaera"</string>
     <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"Parekatu eta konektatu"</string>
-    <string name="bluetooth" msgid="5235115159234688629">"Bluetooth-a"</string>
+    <string name="bluetooth" msgid="5235115159234688629">"Bluetooth konexioa"</string>
     <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"Bluetooth parekatze-kodea"</string>
     <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"PIN kodeak hizkiak edo ikurrak ditu"</string>
     <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"Idatzi parekatze-kodea eta sakatu Itzuli edo Sartu"</string>
     <string name="bluetooth_pairing_request" msgid="4769675459526556801">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> gailuarekin parekatu nahi duzu?"</string>
-    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"Eman zure kontaktuak eta deien historia atzitzeko baimena <xliff:g id="DEVICE_NAME">%1$s</xliff:g> gailuari"</string>
+    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"Baimendu <xliff:g id="DEVICE_NAME">%1$s</xliff:g> gailuari zure kontaktuak eta deien historia atzitzea"</string>
     <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"Baliteke PIN hau beste gailuan ere idatzi behar izatea."</string>
     <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"Baliteke pasahitz hau beste gailuan ere idatzi behar izatea."</string>
     <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"16 digitu izan behar ditu"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Parekatzeko eskaera"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Sakatu <xliff:g id="DEVICE_NAME">%1$s</xliff:g> gailuarekin parekatzeko."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Hizkuntzak"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Soinua"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Tonuaren bolumena"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Nabigazio-bolumena"</string>
@@ -108,7 +107,7 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Ez du baimenik"</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Ez du eskatu baimenik"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Datuen erabilera"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Aplik. datuen erabilera"</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Aplikazioen datuen erabilera"</string>
     <string name="force_stop" msgid="2153183697014720520">"Behartu gelditzera"</string>
     <string name="computing_size" msgid="5791407621793083965">"Kalkulatzen…"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
@@ -142,7 +141,7 @@
     <string name="terms_title" msgid="5201471373602628765">"Zehaztapenak eta baldintzak"</string>
     <string name="webview_license_title" msgid="2531829466541104826">"Sistemaren WebView lizentzia"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"Horma-paperak"</string>
-    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Sateliteko irudien hornitzaileak:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
+    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Satelite-irudien hornitzaileak:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
     <string name="settings_license_activity_title" msgid="8499293744313077709">"Hirugarrenen lizentziak"</string>
     <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"Arazo bat gertatu da lizentziak kargatzean."</string>
     <string name="settings_license_activity_loading" msgid="6163263123009681841">"Kargatzen…"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Ezabatu erabiltzailea"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Erabiltzaile berria"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gonbidatua"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administratzailea"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Administratzaile gisa hasi da saioa"</string>
     <string name="user_switch" msgid="6544839750534690781">"Aldatu"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Zu (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Izena"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Saiatu berriro"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Saltatu"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ezarri pantailaren blokeo bat"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Aukeratu PIN kodea"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Aukeratu eredu bat"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Aukeratu pasahitza"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Uneko pantailaren blokeoa"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Babestuta egoteko, ezarri eredu bat"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Garbitu"</string>
@@ -237,7 +232,7 @@
     <string name="lock_settings_enter_password" msgid="2636669926649496367">"Idatzi pasahitza"</string>
     <string name="choose_lock_pin_message" msgid="2963792070267774417">"Babestuta egoteko, ezarri PIN bat"</string>
     <string name="confirm_your_pin_header" msgid="9096581288537156102">"Idatzi berriro PIN kodea"</string>
-    <string name="choose_lock_pin_hints" msgid="7362906249992020844">"PINak lau digitu izan behar ditu gutxienez"</string>
+    <string name="choose_lock_pin_hints" msgid="7362906249992020844">"Gutxienez 4 digitu izan behar ditu PINak"</string>
     <string name="lockpin_invalid_pin" msgid="2149191577096327424">"PIN kodeak ez du balio: gutxienez lau digitu izan behar ditu."</string>
     <string name="confirm_pins_dont_match" msgid="4607110139373520720">"PIN kodeak ez datoz bat"</string>
     <string name="error_saving_lockpin" msgid="9011960139736000393">"Errore bat gertatu da PINa gordetzean"</string>
@@ -249,16 +244,16 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Garbitu"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Utzi"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Berretsi"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Gutxienez lau karaktere izan behar ditu"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Pasahitzak 4-8 karaktere izan behar ditu, gutxienez zenbaki batekin"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Gutxienez <xliff:g id="COUNT">%d</xliff:g> karaktere izan behar ditu"</string>
-    <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN kodeak <xliff:g id="COUNT">%d</xliff:g> digitu izan behar ditu gutxienez"</string>
+    <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Gutxienez <xliff:g id="COUNT">%d</xliff:g> digitu izan behar ditu PIN kodeak"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Gehienez <xliff:g id="NUMBER">%d</xliff:g> karaktere izan behar ditu"</string>
     <string name="lockpassword_pin_too_long" msgid="62957683396974404">"Gehienez <xliff:g id="NUMBER">%d</xliff:g> digitu izan behar ditu"</string>
     <string name="lockpassword_pin_contains_non_digits" msgid="3044526271686839923">"0 eta 9 arteko zenbakiak soilik izan ditzake."</string>
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Gailuaren administratzaileak ez du eman beste PIN kode bat erabiltzeko baimenik"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"IKT administratzaileak blokeatu egiten ditu asmatzen errazak diren PIN kodeak. Erabili beste PIN bat."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Ezin da erabili onartzen ez den karaktererik."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Pasahitzak ez du balio; gutxienez lau karaktere izan behar ditu."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Pasahitzak ez du balio: 4-8 karaktere izan behar ditu, eta gutxienez zenbaki bat eta hizki bat izan behar ditu, zuriunerik gabe."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Gutxienez <xliff:g id="COUNT">%d</xliff:g> hizki izan behar ditu</item>
       <item quantity="one">Gutxienez 1 hizki izan behar du</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"amaitu konfiguratzen"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"orain ez"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Eginbide hau ezin da erabili gidatu bitartean."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Gidatu bitartean ezin da gehitu erabiltzailerik."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index c9d726c..a9642ab 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"درخواست مرتبط‌سازی"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"برای مرتبط‌سازی با <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ضربه بزنید."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"زبان‌ها"</string>
     <string name="sound_settings" msgid="3072423952331872246">"صدا"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"بلندی صدای زنگ"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"میزان صدای مسیریابی"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"حذف کاربر"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"کاربر جدید"</string>
     <string name="user_guest" msgid="3465399481257448601">"مهمان"</string>
-    <string name="user_admin" msgid="1535484812908584809">"سرپرست"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"به‌عنوان سرپرست به سیستم وارد شدید"</string>
     <string name="user_switch" msgid="6544839750534690781">"جابه‌جایی"</string>
     <string name="current_user_name" msgid="3813671533249316823">"شما (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"نام"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"امتحان مجدد"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"رد شدن"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"تنظیم قفل صفحه"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"پین خودتان را انتخاب کنید"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"انتخاب الگو"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"گذرواژه‌تان را انتخاب کنید"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"قفل صفحه فعلی"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"برای حفظ امنیت، الگو تنظیم کنید"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"پاک کردن"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"پاک کردن"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"لغو"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"تأیید"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"باید حداقل ۴ نویسه داشته باشد"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"گذرواژه باید بین ۴ تا ۸ نویسه و حداقل دارای یک عدد باشد"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"باید حداقل <xliff:g id="COUNT">%d</xliff:g> نویسه داشته باشد"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"پین باید حداقل <xliff:g id="COUNT">%d</xliff:g> رقم داشته باشد"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"باید کمتر از <xliff:g id="NUMBER">%d</xliff:g> نویسه باشد"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"سرپرست دستگاه اجازه استفاده از پین اخیر را نمی‌دهد"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"پین‌های رایج توسط سرپرست فناوری اطلاعات شما مسدود شده‌اند. پین متفاوتی را امتحان کنید."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"نمی‌تواند نویسه نامعتبر داشته باشد."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"گذرواژه نامعتبر است، گذرواژه باید حداقل ۴ نویسه داشته باشد."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"گذرواژه نامعتبر است، باید دارای ۴ تا ۸ نویسه و حداقل ۱ عدد، ۱ حرف و بدون فاصله باشد."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">باید حداقل <xliff:g id="COUNT">%d</xliff:g> حرف داشته باشد</item>
       <item quantity="other">باید حداقل <xliff:g id="COUNT">%d</xliff:g> حرف داشته باشد</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"اتمام راه‌اندازی"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"الان نه"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"هنگام رانندگی، این ویژگی در دسترس نیست."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"نمی‌توان هنگام رانندگی، کاربر اضافه کرد."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index bc6b733..21dc398 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Laiteparipyyntö"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Napauta muodostaaksesi laiteparin: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Kielet"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Ääni"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Soittoäänen voimakkuus"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigoinnin äänenvoimakkuus"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Poista käyttäjä"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Uusi käyttäjä"</string>
     <string name="user_guest" msgid="3465399481257448601">"Vieras"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Järjestelmänvalvoja"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Järjestelmänvalvoja kirjautuneena"</string>
     <string name="user_switch" msgid="6544839750534690781">"Vaihda"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Sinä (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nimi"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Yritä uudelleen"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Ohita"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Näytön lukituksen valinta"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Valitse PIN-koodi"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Valitse kuvio"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Valitse salasana"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Nykyinen näytön lukitus"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Aseta kuvio tietoturvasyistä"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Tyhjennä"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Tyhjennä"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Peruuta"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Vahvista"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Vähimmäispituus on 4 merkkiä."</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Salasanassa on oltava 4–8 merkkiä ja vähintään yksi numero."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Salasanan vähimmäispituus on <xliff:g id="COUNT">%d</xliff:g> merkkiä."</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN-koodin vähimmäispituus on <xliff:g id="COUNT">%d</xliff:g> numeroa."</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Salasanassa saa olla enintään <xliff:g id="NUMBER">%d</xliff:g> merkkiä."</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Järjestelmänvalvoja esti PIN-koodin, koska sitä on käytetty viime aikoina."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"IT-järjestelmänvalvoja on estänyt yleiset PIN-koodit. Kokeile eri PIN-koodia."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Salasanassa ei saa olla virheellisiä merkkejä."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Virheellinen salasana. Vähimmäispituus on 4 merkkiä."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Virheellinen salasana. Salasanassa on oltava 4–8 merkkiä, joista vähintään yksi on numero ja yksi kirjain, eikä yhtään välilyöntiä."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Salasanassa on oltava vähintään <xliff:g id="COUNT">%d</xliff:g> kirjainta.</item>
       <item quantity="one">Salasanassa on oltava vähintään 1 kirjain.</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"viimeistele asennus"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ei nyt"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Ominaisuus ei ole käytettävissä ajon aikana."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Käyttäjää ei voi lisätä ajon aikana."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 3202415..a944077 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Demande d\'association"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Touchez pour associer à <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Langues"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Son"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volume de la sonnerie"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volume de navigation"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Supprimer l\'utilisateur"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nouvel utilisateur"</string>
     <string name="user_guest" msgid="3465399481257448601">"Invité"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrateur"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Connecté comme administrateur"</string>
     <string name="user_switch" msgid="6544839750534690781">"Changer"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Vous (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nom"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Réessayer"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Ignorer"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Configurer le verrouillage de l\'écran"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Choisissez votre NIP"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Choisissez votre schéma"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Choisissez votre mot de passe"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Verrouillage actuel de l\'écran"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Pour votre sécurité, déf. un sch. de verr."</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Effacer"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Effacer"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Annuler"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirmer"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Doit comprendre au moins quatre caractères"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Les mots de passe doivent comprendre entre 4 et 8 caractères et comporter au moins 1 chiffre"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> caractères"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Le NIP doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> chiffres"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Doit contenir moins de <xliff:g id="NUMBER">%d</xliff:g> caractères."</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"L\'administrateur de l\'appareil ne permet pas l\'utilisation d\'un NIP récent"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Les NIP communs sont bloqués par l\'administrateur de votre service informatique. Essayez un NIP différent."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Vous ne pouvez pas inclure de caractère non valide."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Le mot de passe doit comprendre au moins quatre caractères"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Le mot de passe n\'est pas valide, il doit comprendre entre 4 et 8 caractères, et contenir au moins 1 chiffre, 1 lettre et aucune espace."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> lettre</item>
       <item quantity="other">Doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> lettres</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"terminer la configuration"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"pas maintenant"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Cette fonction n\'est pas accessible durant la conduite."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Impossible d\'ajouter un utilisateur pendant la conduite."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index d644eaf..9b2d25c 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Demande d\'association"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Appuyez pour associer l\'appareil à \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Langues"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Son"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volume de la sonnerie"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volume de la navigation"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Supprimer un compte utilisateur"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nouvel utilisateur"</string>
     <string name="user_guest" msgid="3465399481257448601">"Invité"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrateur"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Connecté en tant qu\'administrateur"</string>
     <string name="user_switch" msgid="6544839750534690781">"Changer"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Vous (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nom"</string>
@@ -214,10 +211,8 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Réessayer"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Passer"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Configurer le verrouillage de l\'écran"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Sélectionner un code"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Choisir votre schéma"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Sélectionner un mot de passe"</string>
-    <string name="current_screen_lock" msgid="637651611145979587">"Verrouillage actuel de l\'écran"</string>
+    <string name="current_screen_lock" msgid="637651611145979587">"Verrouillage de l\'écran actuel"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Pour la sécurité, définissez un schéma"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Effacer"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"Annuler"</string>
@@ -243,13 +238,13 @@
     <string name="error_saving_lockpin" msgid="9011960139736000393">"Erreur d\'enregistrement du code"</string>
     <string name="lockscreen_wrong_pin" msgid="4922465731473805306">"Code incorrect"</string>
     <string name="lockscreen_wrong_password" msgid="5757087577162231825">"Mot de passe incorrect"</string>
-    <string name="choose_lock_password_message" msgid="6124341145027370784">"Pour la sécurité, choisissez un mot de passe"</string>
+    <string name="choose_lock_password_message" msgid="6124341145027370784">"Pour la sécurité, choisir un mot de passe"</string>
     <string name="confirm_your_password_header" msgid="7052891840366724938">"Saisissez de nouveau votre mot de passe"</string>
     <string name="confirm_passwords_dont_match" msgid="7300229965206501753">"Mots de passe non concordants"</string>
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Effacer"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Annuler"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirmer"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Le mot de passe doit comporter au moins 4 caractères"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Mot de passe (4-8 car., 1 chiffre min.)"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Le mot de passe doit faire au moins <xliff:g id="COUNT">%d</xliff:g> car."</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Le code doit contenir au moins <xliff:g id="COUNT">%d</xliff:g> chiffres"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Le mot de passe doit faire moins de <xliff:g id="NUMBER">%d</xliff:g> car."</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"L\'administrateur de l\'appareil n\'autorise pas l\'utilisation d\'un code récent"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Les codes courants sont bloqués par votre administrateur informatique. Choisissez un autre code."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Vous ne pouvez pas inclure de caractère non valide."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Mot de passe non valide : il doit comporter au moins 4 caractères."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Mot de passe non valide (il doit comporter entre 4 et 8 caractères, au moins un chiffre, une lettre et aucun espace blanc)."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Le mot de passe doit comporter au moins <xliff:g id="COUNT">%d</xliff:g> lettre</item>
       <item quantity="other">Le mot de passe doit comporter au moins <xliff:g id="COUNT">%d</xliff:g> lettres</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"terminer la configuration"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"pas maintenant"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Fonctionnalité non disponible lorsque vous conduisez."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Impossible d\'ajouter un utilisateur pendant la conduite."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index e374aaa..3f71b75 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -64,7 +64,7 @@
     <string name="bluetooth_talkback_computer" msgid="5223330129934365312">"Ordenador"</string>
     <string name="bluetooth_talkback_headset" msgid="6155254514321149935">"Auriculares"</string>
     <string name="bluetooth_talkback_phone" msgid="8833977851215000426">"Teléfono"</string>
-    <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"Dispositivo de imaxe"</string>
+    <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"Imaxes"</string>
     <string name="bluetooth_talkback_headphone" msgid="5362155791551671490">"Auriculares"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="868933277567862622">"Periférico de entrada"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"Bluetooth"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Solicitude de sincronización"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Toca para sincronizar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Idiomas"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Son"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volume do timbre"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volume de navegación"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Eliminar usuario"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Novo usuario"</string>
     <string name="user_guest" msgid="3465399481257448601">"Convidado"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrador"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Iniciouse sesión como administrador"</string>
     <string name="user_switch" msgid="6544839750534690781">"Cambiar"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Ti (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nome"</string>
@@ -214,21 +211,19 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Tentar de novo"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Omitir"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Definir un bloqueo de pantalla"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Escolle o teu PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Seleccionar o teu padrón"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Escolle o teu contrasinal"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Bloqueo de pantalla actual"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Por cuestións de seguranza, define un padrón"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Borrar"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"Cancelar"</string>
-    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"O teu novo padrón de desbloqueo"</string>
-    <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"Debuxa un padrón de desbloqueo"</string>
+    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"O teu novo deseño de desbloqueo"</string>
+    <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"Debuxa un deseño de desbloqueo"</string>
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Solta o dedo cando remates"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"Rexistrouse o padrón"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"Debuxa o padrón de novo para confirmalo"</string>
     <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Conecta polo menos 4 puntos e téntao de novo."</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"O padrón é incorrecto"</string>
-    <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"Como debuxar un padrón de desbloqueo"</string>
+    <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"Como debuxar un deseño de desbloqueo"</string>
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"Erro ao gardar o padrón"</string>
     <string name="okay" msgid="4589873324439764349">"Aceptar"</string>
     <string name="remove_screen_lock_title" msgid="1234382338764193387">"Eliminar bloqueo de pantalla?"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Borrar"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancelar"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirmar"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Debe ter como mínimo 4 caracteres"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"O contrasinal debe ter 4-8 caracteres e polo menos 1 número"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Debe ter polo menos <xliff:g id="COUNT">%d</xliff:g> caracteres"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"O PIN debe ter polo menos <xliff:g id="COUNT">%d</xliff:g> díxitos"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Debe conter menos de <xliff:g id="NUMBER">%d</xliff:g> caracteres"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"O administrador de dispositivos non permite o uso dun PIN recente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"O teu administrador de TI bloqueou os PIN comúns. Proba cun PIN diferente."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Non pode conter un carácter que non é válido."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"O contrasinal non é válido. Debe ter como mínimo 4 caracteres."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"O contrasinal non é válido: debe ter entre 4 e 8 caracteres, ademais de incluír polo menos 1 díxito e 1 letra, e non ter espazos en branco."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Debe conter polo menos <xliff:g id="COUNT">%d</xliff:g> letras</item>
       <item quantity="one">Debe conter polo menos 1 letra</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"finalizar configuración"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"agora non"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Esta función non está dispoñible mentres conduces."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Non se poden engadir usuarios mentres se conduce."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 38e0676..35c13ce 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"જોડાણ બનાવવાની વિનંતી"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> સાથે જોડાણ બનાવવા માટે ટૅપ કરો."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ભાષાઓ"</string>
     <string name="sound_settings" msgid="3072423952331872246">"સાઉન્ડ"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"રિંગ વૉલ્યૂમ"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"નૅવિગેશન વૉલ્યૂમ"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"વપરાશકર્તા ડિલીટ કરો"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"નવા વપરાશકર્તા"</string>
     <string name="user_guest" msgid="3465399481257448601">"અતિથિ"</string>
-    <string name="user_admin" msgid="1535484812908584809">"વ્યવસ્થાપક"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"વ્યવસ્થાપક તરીકે સાઇન ઇન થયા"</string>
     <string name="user_switch" msgid="6544839750534690781">"સ્વિચ કરો"</string>
     <string name="current_user_name" msgid="3813671533249316823">"તમે (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"નામ"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ફરી પ્રયાસ કરો"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"છોડો"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"સ્ક્રીન લૉક સેટ કરો"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"તમારો પિન પસંદ કરો"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"તમારી પૅટર્ન પસંદ કરો"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"તમારો પાસવર્ડ પસંદ કરો"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"વર્તમાન સ્ક્રીન લૉક"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"સુરક્ષા માટે, એક પૅટર્ન સેટ કરો"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"સાફ કરો"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"સાફ કરો"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"રદ કરો"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"કન્ફર્મ કરો"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"ઓછામાં ઓછા 4 અક્ષર હોવા આવશ્યક છે"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"પાસવર્ડ ઓછામાં ઓછો 1 અંક ધરાવતો, 4-8 અક્ષરોની વચ્ચેનો હોવો આવશ્યક છે"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"ઓછામાં ઓછા <xliff:g id="COUNT">%d</xliff:g> અક્ષર હોવા આવશ્યક છે"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"પિન ઓછામાં ઓછા <xliff:g id="COUNT">%d</xliff:g> અંક ધરાવતો હોવો જોઈએ"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> કરતાં ઓછા અક્ષર હોવા આવશ્યક છે"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ઉપકરણ વ્યવસ્થાપક તાજેતરના પિનનો ઉપયોગ કરવાની મંજૂરી આપતા નથી"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"સામાન્ય પિન તમારા IT વ્યવસ્થાપક દ્વારા બ્લૉક કરવામાં આવેલા છે. એક અલગ પિન અજમાવી જુઓ."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"આમાં અમાન્ય અક્ષરોનો સમાવેશ થઈ શકતો નથી."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"પાસવર્ડ અમાન્ય છે, ઓછામાં ઓછા 4 અક્ષર હોવા આવશ્યક છે."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"પાસવર્ડ અમાન્ય, તે 4-8 અક્ષરોનો, ઓછોમાં ઓછો 1 અંક, 1 વર્ણ ધરાવતો હોવો જોઈએ અને કોઈ વ્હાઇટસ્પેસ ન હોવી જોઈએ."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">ઓછામાં ઓછા <xliff:g id="COUNT">%d</xliff:g> અક્ષર ધરાવતો હોવો જોઈએ</item>
       <item quantity="other">ઓછામાં ઓછા <xliff:g id="COUNT">%d</xliff:g> અક્ષર ધરાવતો હોવો જોઈએ</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ડેમોમાંથી બહાર નીકળો"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"સેટઅપ સમાપ્ત કરો"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"હમણાં નહીં"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ડ્રાઇવિંગ કરતી વખતે આ સુવિધા ઉપલબ્ધ રહેશે નહીં."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ડ્રાઇવ કરતી વખતે વપરાશકર્તાને ઉમેરી શકાતા નથી."</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index c04da3c..bade60f 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"दूसरे डिवाइस से जोड़ने का अनुरोध किया गया"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> से जोड़ने के लिए टैप करें."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"भाषाएं"</string>
     <string name="sound_settings" msgid="3072423952331872246">"आवाज़"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"घंटी बजने की आवाज़"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"नेविगेशन की आवाज़"</string>
@@ -123,7 +122,7 @@
     <string name="model_info" msgid="4966408071657934452">"मॉडल"</string>
     <string name="baseband_version" msgid="2370088062235041897">"मोबाइल रेडियो (बेसबैंड वर्शन)"</string>
     <string name="kernel_version" msgid="7327212934187011508">"Kernel का वर्शन"</string>
-    <string name="build_number" msgid="3997326631001009102">"बिल्‍ड नंबर"</string>
+    <string name="build_number" msgid="3997326631001009102">"बिल्‍ड नबंर"</string>
     <string name="device_info_not_available" msgid="2095601973977376655">"उपलब्ध नहीं है"</string>
     <string name="device_status_activity_title" msgid="4083567497305368200">"स्थिति"</string>
     <string name="device_status" msgid="267298179806290920">"स्थिति"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"उपयोगकर्ता (का नाम) मिटाएं"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"नया उपयोगकर्ता"</string>
     <string name="user_guest" msgid="3465399481257448601">"मेहमान"</string>
-    <string name="user_admin" msgid="1535484812908584809">"एडमिन"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"एडमिन के तौर पर साइन-इन किया है"</string>
     <string name="user_switch" msgid="6544839750534690781">"स्विच करें"</string>
     <string name="current_user_name" msgid="3813671533249316823">"आप (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"नाम"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"फिर से कोशिश करें"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"छोड़ें"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"स्क्रीन लॉक सेट करना"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"अनलॉक करने के लिए पिन चुनें"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"अनलॉक करने के लिए पैटर्न चुनें"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"अनलॉक करने के लिए पासवर्ड चुनें"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"मौजूदा स्क्रीन लॉक"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"सुरक्षा के लिए पैटर्न सेट करें"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"हटाएं"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"हटाएं"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"अभी नहीं"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"पुष्टि करें"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"पासवर्ड में कम से कम 4 वर्ण होने चाहिए"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"पासवर्ड में 4 से 8 वर्ण होने चाहिए, जिसमें कम से कम एक अंक होना चाहिए"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"पासवर्ड में कम से कम <xliff:g id="COUNT">%d</xliff:g> वर्ण होने चाहिए"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"पिन में कम से कम <xliff:g id="COUNT">%d</xliff:g> अंक होने चाहिए"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"पासवर्ड में <xliff:g id="NUMBER">%d</xliff:g> से कम वर्ण होने चाहिए"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"डिवाइस एडमिन हाल ही का पिन इस्तेमाल करने की मंज़ूरी नहीं देता"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"आपके आईटी एडमिन ने आम तौर पर इस्तेमाल होने वाले पिन पर रोक लगा रखी है. दूसरा पिन बनाएं."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"इसमें अमान्य वर्ण नहीं डाला जा सकता."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"यह पासवर्ड अमान्य है, इसमें कम से कम 4 वर्ण होने चाहिए"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"पासवर्ड अमान्य है. इसमें 4 से 8 वर्ण होने चाहिए, जिसमें कम से कम 1 अंक और 1 अक्षर हो और बीच में कोई खाली जगह न हो."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">पासवर्ड में कम से कम <xliff:g id="COUNT">%d</xliff:g> अक्षर होने चाहिए</item>
       <item quantity="other">पासवर्ड में कम से कम <xliff:g id="COUNT">%d</xliff:g> अक्षर होने चाहिए</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"सेटअप पूरा करें"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"अभी नहीं"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"गाड़ी चलाते समय इस सुविधा का इस्तेमाल नहीं किया जा सकता."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"गाड़ी चलाते समय उपयोगकर्ता को जोड़ा नहीं जा सकता."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index d8f0a22..7c2e0f3 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -32,8 +32,8 @@
     <string name="night_mode_tile_label" msgid="6603597795502131664">"Noćni način rada"</string>
     <string name="wifi_settings" msgid="7701477685273103841">"Wi-Fi"</string>
     <string name="wifi_settings_summary" msgid="6095898149997291025">"Postavljanje i upravljanje bežičnim pristupnim točkama"</string>
-    <string name="wifi_starting" msgid="473253087503153167">"Uključivanje Wi-Fija…"</string>
-    <string name="wifi_stopping" msgid="3534173972547890148">"Isključivanje Wi-Fija…"</string>
+    <string name="wifi_starting" msgid="473253087503153167">"Uključivanje Wi-Fi-ja…"</string>
+    <string name="wifi_stopping" msgid="3534173972547890148">"Isključivanje Wi-Fi-ja…"</string>
     <string name="wifi_failed_forget_message" msgid="121732682699377206">"Zaboravljanje mreže nije uspjelo"</string>
     <string name="wifi_failed_connect_message" msgid="4447498225022147324">"Povezivanje s mrežom nije uspjelo"</string>
     <string name="wifi_setup_add_network" msgid="3660498520389954620">"Dodaj mrežu"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Zahtjev za uparivanje"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Dodirnite za uparivanje s uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Jezici"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Zvuk"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Glasnoća zvona"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Glasnoća navigacije"</string>
@@ -171,8 +170,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Brisanje korisnika"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Novi korisnik"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gost"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Prijavljeni ste kao administrator"</string>
     <string name="user_switch" msgid="6544839750534690781">"Prebaci"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Vi (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Ime"</string>
@@ -215,9 +212,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Pokušaj ponovno"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Preskoči"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Postavljanje zaključavanja zaslona"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Odaberite PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Odaberite uzorak"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Odaberite zaporku"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Trenutačno zaključavanje zaslona"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Radi sigurnosti postavite uzorak"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Ukloni"</string>
@@ -250,7 +245,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Ukloni"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Otkaži"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Potvrdi"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Mora imati najmanje četiri znaka"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Zaporka mora imati od četiri do osam znakova i najmanje jedan broj"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Broj znakova ne smije biti manji od <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Broj znamenki u PIN-u ne smije biti manji od <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Mora imati manje od ovoliko znakova: <xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -259,7 +254,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Administrator uređaja ne dopušta upotrebu nedavnog PIN-a"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Vaš je IT administrator blokirao uobičajene PIN-ove. Pokušajte s nekom drugim PIN-om."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Unos ne smije sadržavati nevažeće znakove."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Zaporka nije važeća, mora imati najmanje četiri znaka."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Zaporka nije važeća. Mora imati od četiri do osam znakova, najmanje jednu znamenku i jedno slovo i ne smije sadržavati razmake."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> slovo</item>
       <item quantity="few">Mora sadržavati najmanje <xliff:g id="COUNT">%d</xliff:g> slova</item>
@@ -308,5 +303,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"dovršite postavljanje"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ne sada"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Značajka nije dostupna tijekom vožnje."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Nije moguće dodati korisnika tijekom vožnje."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 182abb8..64a8069 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Párosítási kérelem"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Koppintson a(z) <xliff:g id="DEVICE_NAME">%1$s</xliff:g> eszközzel való párosításhoz."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Nyelvek"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Hang"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Csengés hangereje"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigáció hangereje"</string>
@@ -136,7 +135,7 @@
     <string name="contributors_title" msgid="7698463793409916113">"Közreműködők"</string>
     <string name="manual" msgid="4819839169843240804">"Kézi"</string>
     <string name="regulatory_labels" msgid="3165587388499646779">"Szabályozási címkék"</string>
-    <string name="safety_and_regulatory_info" msgid="1204127697132067734">"Biztonsági és az előírásokkal kapcsolatos útmutató"</string>
+    <string name="safety_and_regulatory_info" msgid="1204127697132067734">"Biztonsági és szabályozási útmutató"</string>
     <string name="copyright_title" msgid="4220237202917417876">"Szerzői jog"</string>
     <string name="license_title" msgid="936705938435249965">"Licenc"</string>
     <string name="terms_title" msgid="5201471373602628765">"Általános Szerződési Feltételek"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Felhasználó törlése"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Új felhasználó"</string>
     <string name="user_guest" msgid="3465399481257448601">"Vendég"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Rendszergazda"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Rendszergazdaként bejelentkezve"</string>
     <string name="user_switch" msgid="6544839750534690781">"Váltás"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Ön (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Név"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Újra"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Kihagyás"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Képernyőzár beállítása"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PIN-kód kiválasztása"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Válassza ki a mintát"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Jelszó kiválasztása"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Jelenlegi képernyőzár"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"A biztonság érdekében állítson be mintát"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Törlés"</string>
@@ -226,7 +221,7 @@
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Emelje fel az ujját, ha kész"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"Minta rögzítve"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"Rajzolja újra a mintát a megerősítéshez"</string>
-    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Kössön össze legalább 4 pontot."</string>
+    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Kapcsoljon össze legalább 4 pontot."</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"Helytelen minta"</string>
     <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"Hogyan kell feloldási mintát rajzolni?"</string>
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"Hiba történt a minta mentése során"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Törlés"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Mégse"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Megerősítés"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Legalább 4 karakterből kell állnia"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Jelszó: 4–8 karakter, legalább egy szám"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Legalább <xliff:g id="COUNT">%d</xliff:g> karakter hosszúnak kell lennie"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"A PIN-kód legalább <xliff:g id="COUNT">%d</xliff:g> számjegy legyen"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> karakternél rövidebbnek kell lennie"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Az eszközrendszergazda nem engedélyezi a legutóbbi PIN-kódok használatát"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Rendszergazdája letiltotta a gyakran használt PIN-kódokat. Próbálkozzon másik PIN-kóddal."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Nem tartalmazhat érvénytelen karaktert."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Érvénytelen jelszó, legalább 4 karakterből kell állnia."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"A jelszó érvénytelen. 4–8 karaktert, legalább egy számot, legalább egy betűt kell tartalmaznia, és nem lehet benne szóköz."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Tartalmaznia kell legalább <xliff:g id="COUNT">%d</xliff:g> betűt</item>
       <item quantity="one">Tartalmaznia kell legalább 1 betűt</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"beállítás befejezése"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"most nem"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Vezetés közben nem áll rendelkezésre a funkció."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Vezetés közben nem lehet hozzáadni felhasználót."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 119f0cb..fdf80af 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Զուգակցման հարցում"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Հպեք` <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ զուգակցելու համար:"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Լեզուներ"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Ձայն"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Զանգի ձայնը"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Նավիգացիայի ձայնը"</string>
@@ -109,7 +108,7 @@
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Թույլտվությունների հարցում չի արվել"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Թրաֆիկի օգտագործում"</string>
     <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Հավելվածի թրաֆիկ"</string>
-    <string name="force_stop" msgid="2153183697014720520">"Կանգնեցնել"</string>
+    <string name="force_stop" msgid="2153183697014720520">"Ստիպողաբար դադարեցնել"</string>
     <string name="computing_size" msgid="5791407621793083965">"Հաշվարկում…"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
       <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> լրացուցիչ թույլտվություն</item>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Ջնջել օգտատիրոջը"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Նոր օգտատեր"</string>
     <string name="user_guest" msgid="3465399481257448601">"Հյուր"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Ադմինիստրատոր"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Դուք մտել եք որպես ադմինիստրատոր"</string>
     <string name="user_switch" msgid="6544839750534690781">"Փոխել օգտատիրոջը"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Դուք (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Անուն"</string>
@@ -213,10 +210,8 @@
     <string name="continue_button_text" msgid="5129979170426836641">"Շարունակել"</string>
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Կրկնել"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Բաց թողնել"</string>
-    <string name="set_screen_lock" msgid="5239317292691332780">"Էկրանի կողպում"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Ընտրեք ձեր PIN-ը"</string>
+    <string name="set_screen_lock" msgid="5239317292691332780">"Էկրանի կողպման կարգավորում"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Ընտրեք ձեր նախշը"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Ընտրեք ձեր գաղտնաբառը"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Կողպման ընթացիկ եղանակը"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Անվտանգության համար ավելացրեք նախշ"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Մաքրել"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Մաքրել"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Չեղարկել"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Հաստատել"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Պետք է լինի առնվազն 4 նիշ"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Գաղտնաբառ՝ 4-8 նիշ (առնվազն 1 թիվ)"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Պետք է պարունակի առնվազն <xliff:g id="COUNT">%d</xliff:g> նիշ"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN-ը պետք է պարունակի առնվազն <xliff:g id="COUNT">%d</xliff:g> թվանշան"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Պետք է <xliff:g id="NUMBER">%d</xliff:g>-ից քիչ նիշ պարունակի"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Սարքի ադմինիստրատորը չի թույլատրում օգտագործել վերջին PIN կոդերը"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ՏՏ ադմինիստրատորն արգելափակել է պարզ PIN կոդերը: Փորձեք մեկ այլ PIN:"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Գաղտնաբառն անվավեր նիշ է պարունակում:"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Գաղտնաբառը պետք է պարունակի առնվազն 4 նիշ:"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Գաղտնաբառն անվավեր է. պետք է լինի 4-8 նիշ և պարունակի առնվազն 1 թիվ ու 1 տառ՝ առանց բացատի:"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Պետք է պարունակի առնվազն <xliff:g id="COUNT">%d</xliff:g> տառ</item>
       <item quantity="other">Պետք է պարունակի առնվազն <xliff:g id="COUNT">%d</xliff:g> տառ</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ավարտել կարգավորումը"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ոչ հիմա"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Վարելու ընթացքում գործառույթը հասանելի չէ:"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Վարելու ժամանակ հնարավոր չէ օգտատեր ավելացնել:"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index d9415f3..5716289 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Permintaan penyambungan"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tap untuk menyambungkan dengan <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Bahasa"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Suara"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volume dering"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volume navigasi"</string>
@@ -108,7 +107,7 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Tidak ada izin yang diberikan"</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Tidak ada izin yang diminta"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Penggunaan data"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Penggunaan kuota aplikasi"</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Penggunaan data aplikasi"</string>
     <string name="force_stop" msgid="2153183697014720520">"Paksa berhenti"</string>
     <string name="computing_size" msgid="5791407621793083965">"Menghitung..."</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Menghapus pengguna"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Pengguna baru"</string>
     <string name="user_guest" msgid="3465399481257448601">"Tamu"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Admin"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Sedang login admin"</string>
     <string name="user_switch" msgid="6544839750534690781">"Ganti"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Anda (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nama"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Coba lagi"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Lewati"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Setel kunci layar"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Pilih PIN Anda"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Pilih pola Anda"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Pilih sandi Anda"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Kunci layar saat ini"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Untuk keamanan, setel pola"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Hapus"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Hapus"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Batal"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Konfirmasi"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Minimal 4 karakter"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Sandi harus terdiri dari 4-8 karakter dengan minimal 1 angka"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Minimal berisi <xliff:g id="COUNT">%d</xliff:g> karakter"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN minimal <xliff:g id="COUNT">%d</xliff:g> digit"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Harus kurang dari <xliff:g id="NUMBER">%d</xliff:g> karakter"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Admin perangkat tidak mengizinkan penggunaan PIN yang baru-baru ini digunakan"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"PIN umum diblokir oleh admin IT. Coba PIN lain."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Tidak boleh berisi karakter yang tidak valid."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Sandi tidak valid, minimal 4 karakter."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Sandi tidak valid, harus terdiri dari 4-8 karakter, minimal berisi 1 digit, 1 huruf, tanpa spasi kosong."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Minimal berisi <xliff:g id="COUNT">%d</xliff:g> huruf</item>
       <item quantity="one">Minimal berisi 1 huruf</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"selesaikan penyiapan"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"lain kali"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Fitur tidak tersedia saat mengemudi."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Tidak dapat menambahkan pengguna saat mengemudi."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 8159267..7912f41 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Beiðni um pörun"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Ýttu til að para við <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Tungumál"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Hljóð"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Hljóðstyrkur hringingar"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Hljóðstyrkur leiðsagnar"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Eyða notanda"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nýr notandi"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gestur"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Stjórnandi"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Skráð(ur) inn sem stjórnandi"</string>
     <string name="user_switch" msgid="6544839750534690781">"Skipta"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Þú (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nafn"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Reyna aftur"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Sleppa"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Setja upp skjálás"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Veldu þér PIN-númer"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Veldu þér mynstur"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Veldu þér aðgangsorð"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Núverandi skjálás"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Settu upp mynstur til að gæta fyllsta öryggis"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Hreinsa"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Hreinsa"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Hætta við"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Staðfesta"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Verður að vera að minnsta kosti fjórir stafir"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Aðgangsorð þarf að vera 4–8 stafir og innihalda minnst einn tölustaf"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Verður að vera að minnsta kosti <xliff:g id="COUNT">%d</xliff:g> stafir"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN-númerið verður að vera að minnsta kosti <xliff:g id="COUNT">%d</xliff:g> tölustafir"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Verður að vera styttra en <xliff:g id="NUMBER">%d</xliff:g> stafir"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Tækjastjóri leyfir ekki notkun nýlegs PIN-númers"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Kerfisstjórinn þinn hefur lokað á algeng PIN-númer. Prófaðu annað PIN-númer."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Þetta má ekki innihalda ógildan staf."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Aðgangsorðið er ógilt, það verður að vera minnst fjórir stafir að lengd."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Aðgangsorðið er ógilt. Það þarf að vera 4–8 stafir og verður að innihalda minnst einn tölustaf og einn bókstaf, án bila."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Verður að innihalda að minnsta kosti <xliff:g id="COUNT">%d</xliff:g> staf</item>
       <item quantity="other">Verður að innihalda að minnsta kosti <xliff:g id="COUNT">%d</xliff:g> stafi</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ljúka við uppsetningu"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ekki núna"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Þessi eiginleiki er ekki í boði á meðan þú ekur."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Ekki er hægt að bæta við notanda meðan á akstri stendur."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index a6f1e66..be13bc9 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Richiesta accoppiamento"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tocca per l\'accoppiamento con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Lingue"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Audio"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volume suoneria"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volume per la navigazione"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Elimina utente"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nuovo utente"</string>
     <string name="user_guest" msgid="3465399481257448601">"Ospite"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Amministratore"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Accesso eseguito da amministratore"</string>
     <string name="user_switch" msgid="6544839750534690781">"Cambia"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Tu (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nome"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Riprova"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Ignora"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Imposta un blocco schermo"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Scegli il PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Scegli la tua sequenza"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Scegli la password"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Blocco schermo attuale"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Per sicurezza, imposta una sequenza"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Cancella"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Cancella"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Annulla"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Conferma"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Deve contenere almeno 4 caratteri"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"La password deve avere 4-8 caratteri e almeno un numero"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Deve contenere almeno <xliff:g id="COUNT">%d</xliff:g> caratteri"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Il PIN deve contenere almeno <xliff:g id="COUNT">%d</xliff:g> cifre"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Deve contenere meno di <xliff:g id="NUMBER">%d</xliff:g> caratteri"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"L\'amministratore del dispositivo non consente l\'utilizzo di un PIN recente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"I PIN comuni sono stati bloccati dall\'amministratore IT. Prova a usare un altro PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Non può contenere un carattere non valido."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Password non valida: deve avere almeno 4 caratteri."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Password non valida: deve contenere 4-8 caratteri con almeno un numero e una lettera e non deve includere spazi."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Deve contenere almeno <xliff:g id="COUNT">%d</xliff:g> lettere</item>
       <item quantity="one">Deve contenere almeno 1 lettera</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"termina configurazione"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"non ora"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funzione non disponibile durante la guida."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Impossibile aggiungere l\'utente durante la guida."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index a4a8aa5..d91ccfc 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -91,12 +91,11 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"בקשת התאמה"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"יש להקיש כדי ליצור התאמה עם <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"שפות"</string>
     <string name="sound_settings" msgid="3072423952331872246">"צליל"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"עוצמת הצלצול"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"עוצמת קול של ניווט"</string>
     <string name="incoming_call_volume_title" msgid="6972117872424656876">"רינגטון"</string>
-    <string name="notification_volume_title" msgid="6749411263197157876">"התראה"</string>
+    <string name="notification_volume_title" msgid="6749411263197157876">"הודעה"</string>
     <string name="media_volume_title" msgid="6697416686272606865">"מדיה"</string>
     <string name="media_volume_summary" msgid="2961762827637127239">"הגדרת עוצמת קול למוזיקה ולסרטונים"</string>
     <string name="alarm_volume_title" msgid="840384014895796587">"התראה"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"מחיקת משתמש"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"משתמש חדש"</string>
     <string name="user_guest" msgid="3465399481257448601">"אורח"</string>
-    <string name="user_admin" msgid="1535484812908584809">"מנהל מערכת"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"כניסה בתור מנהל מערכת"</string>
     <string name="user_switch" msgid="6544839750534690781">"החלפה"</string>
     <string name="current_user_name" msgid="3813671533249316823">"את/ה (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"שם"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ניסיון חוזר"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"דילוג"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"הגדרת מסך נעילה"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"בחירת קוד גישה"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"הגדרת הקו לביטול נעילה"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"בחירת סיסמה"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"נעילת המסך הנוכחית"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"מטעמי אבטחה, יש להגדיר קו ביטול נעילה"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"ניקוי"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"ניקוי"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"ביטול"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"אישור"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"הסיסמה חייבת להיות באורך של ארבעה תווים לפחות"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"הסיסמה צריכה להכיל בין 4-8 תווים עם מספר אחד לפחות"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"צריכה להכיל <xliff:g id="COUNT">%d</xliff:g> תווים לפחות"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"קוד הגישה צריך להיות באורך של <xliff:g id="COUNT">%d</xliff:g> ספרות לפחות"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"צריכה להכיל פחות מ-<xliff:g id="NUMBER">%d</xliff:g> תווים"</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"מנהל המכשיר לא מאפשר להשתמש בקוד גישה שנעשה בו שימוש לאחרונה"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"קודי גישה נפוצים חסומים בידי מנהל ה-IT. יש לנסות קוד גישה אחר."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"לא ניתן לכלול תו לא חוקי."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"הסיסמה לא חוקית, היא חייבת להיות באורך של ארבעה תווים לפחות."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"הסיסמה לא חוקית, חייבת להכיל: 4-8 תווים, לפחות ספרה אחת, אות אחת, ללא רווח לבן."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="two">צריכה להכיל לפחות <xliff:g id="COUNT">%d</xliff:g> אותיות</item>
       <item quantity="many">צריכה להכיל לפחות <xliff:g id="COUNT">%d</xliff:g> אותיות</item>
@@ -314,6 +309,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"יציאה ממצב הדגמה"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"סיום ההגדרה"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"לא עכשיו"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"התכונה לא זמינה במהלך הנהיגה."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"לא ניתן להוסיף משתמש בזמן הנהיגה."</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 00c1a08..287f1ff 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -24,7 +24,7 @@
     <string name="brightness" msgid="2919605130898772866">"明るさのレベル"</string>
     <string name="auto_brightness_title" msgid="9124647862844666581">"明るさの自動調節"</string>
     <string name="auto_brightness_summary" msgid="4741887033140384352">"周囲に合わせて明るさを最適化する"</string>
-    <string name="condition_night_display_title" msgid="3777509730126972675">"夜間モード ON"</string>
+    <string name="condition_night_display_title" msgid="3777509730126972675">"読書灯 ON"</string>
     <string name="keywords_display" msgid="3978416985146943922">"画面、タッチスクリーン"</string>
     <string name="keywords_display_brightness_level" msgid="3956411572536209195">"画面を暗くする、タッチスクリーン、電池"</string>
     <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"画面を暗くする、タッチスクリーン、電池"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"ペア設定リクエスト"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」とペア設定するにはタップしてください。"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"言語"</string>
     <string name="sound_settings" msgid="3072423952331872246">"音"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"着信音の音量"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"ナビの音量"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ユーザーの削除"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"新しいユーザー"</string>
     <string name="user_guest" msgid="3465399481257448601">"ゲスト"</string>
-    <string name="user_admin" msgid="1535484812908584809">"管理者"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"管理者としてログインしました"</string>
     <string name="user_switch" msgid="6544839750534690781">"切り替える"</string>
     <string name="current_user_name" msgid="3813671533249316823">"自分(%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"名前"</string>
@@ -188,7 +185,7 @@
     <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"制限付きプロファイルではアカウントを追加できません"</string>
     <string name="remove_account_title" msgid="8840386525787836381">"アカウントを削除"</string>
     <string name="really_remove_account_title" msgid="3555164432587924900">"アカウントを削除しますか?"</string>
-    <string name="really_remove_account_message" msgid="4296769280849579900">"このアカウントを削除すると、メール、連絡先などのすべてのデータもデバイスから削除されます。"</string>
+    <string name="really_remove_account_message" msgid="4296769280849579900">"このアカウントを削除すると、メール、連絡先などのすべてのデータも端末から削除されます。"</string>
     <string name="remove_account_failed" msgid="7472511529086294087">"この変更は管理者によって許可されていません"</string>
     <string name="really_remove_user_title" msgid="4990029019291756762">"ユーザーを削除しますか?"</string>
     <string name="really_remove_user_message" msgid="3828090902833944533">"すべてのアプリとデータが削除されます。"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"再試行"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"スキップ"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"画面ロックの設定"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PIN の選択"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"パターンの選択"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"パスワードの選択"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"現在の画面ロック"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"セキュリティ強化のためにパターンを設定"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"消去"</string>
@@ -249,16 +244,16 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"消去"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"キャンセル"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"確認"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"4 文字以上で入力してください"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"パスワードは 4~8 文字で、数字を 1 つ以上含めてください"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"<xliff:g id="COUNT">%d</xliff:g> 文字以上にしてください"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN は <xliff:g id="COUNT">%d</xliff:g> 桁以上必要です"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> 文字未満にしてください"</string>
     <string name="lockpassword_pin_too_long" msgid="62957683396974404">"<xliff:g id="NUMBER">%d</xliff:g> 桁未満にしてください"</string>
     <string name="lockpassword_pin_contains_non_digits" msgid="3044526271686839923">"使用できるのは 0~9 の数字のみです。"</string>
-    <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"デバイス管理により、最近使用した PIN は使用できません"</string>
+    <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"端末管理により、最近使用した PIN は使用できません"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"一般的な PIN は IT 管理者によってブロックされています。別の PIN をお試しください。"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"無効な文字があります。"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"パスワードが無効です。4 文字以上で入力してください。"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"パスワードが無効です。4~8 文字で指定してください。数字と英字が 1 つ以上必要です。空白文字は使用できません。"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">英字が <xliff:g id="COUNT">%d</xliff:g> 文字以上必要です</item>
       <item quantity="one">英字が 1 文字以上必要です</item>
@@ -283,7 +278,7 @@
       <item quantity="other">記号または数字が <xliff:g id="COUNT">%d</xliff:g> つ以上必要です</item>
       <item quantity="one">記号または数字が 1 つ以上必要です</item>
     </plurals>
-    <string name="lockpassword_password_recently_used" msgid="8255729487108602924">"デバイス管理により、最近使用したパスワードは使用できません"</string>
+    <string name="lockpassword_password_recently_used" msgid="8255729487108602924">"端末管理により、最近使用したパスワードは使用できません"</string>
     <string name="error_saving_password" msgid="8334882262622500658">"パスワードの保存中にエラーが発生しました"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"一般的なパスワードは IT 管理者によってブロックされています。別のパスワードをお試しください。"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="38813552228809240">"一連の数字を昇順や降順にしたり、繰り返したりすることはできません。"</string>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"設定を完了"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"後で行う"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"この機能は運転中は利用できません。"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"運転中はユーザーを追加できません。"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 83df5ff..c4f2ba3 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"დაწყვილების თხოვნა"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"შეეხეთ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-თან დასაწყვილებლად."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ენები"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ხმა"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"ზარის სიმძლავრე"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"ნავიგაციის ხმა"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"მომხმარებლის წაშლა"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"ახალი მომხმარებელი"</string>
     <string name="user_guest" msgid="3465399481257448601">"სტუმარი"</string>
-    <string name="user_admin" msgid="1535484812908584809">"ადმინისტრატორი"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"შესული ხართ, როგორც ადმინისტრატორი"</string>
     <string name="user_switch" msgid="6544839750534690781">"გადართვა"</string>
     <string name="current_user_name" msgid="3813671533249316823">"თქვენ (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"სახელი"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ხელახლა ცდა"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"გამოტოვება"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"ეკრანის დაბლოკვის მეთოდის დაყენება"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"თქვენი PIN-კოდის არჩევა"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"აირჩიეთ განმბლოკავი ნიმუში"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"აირჩიეთ პაროლი"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"ეკრანის დაბლოკვის ამჟამინდელი მეთოდი"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"უსაფრთხოებისთვის, დააყენეთ განსაბლოკი ნიმუში"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"გასუფთავება"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"გასუფთავება"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"გაუქმება"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"დადასტურება"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"უნდა შეიცავდეს მინიმუმ 4 სიმბოლოს"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"პაროლი უნდა შეიცავდეს 4-8 სიმბოლოს და მინიმუმ 1 ციფრს"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"უნდა შეიცავდეს მინიმუმ <xliff:g id="COUNT">%d</xliff:g> სიმბოლოს"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN-კოდი უნდა შედგებოდეს მინიმუმ <xliff:g id="COUNT">%d</xliff:g> ციფრისგან"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"უნდა შეიცავდეს <xliff:g id="NUMBER">%d</xliff:g> სიმბოლოზე ნაკლებს"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ბოლოდროინდელი PIN-კოდის ხელახლა გამოყენება მოწყობილობის ადმინისტრატორის მიერ ნებადართული არ არის"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ხშირად გამოყენებული PIN-კოდები თქვენი IT ადმინისტრატორის მიერ დაბლოკილია. ცადეთ სხვა PIN-კოდი."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"არ უნდა შეიცავდეს არასწორ სიმბოლოს."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"პაროლი არასწორია, უნდა შეიცავდეს მინიმუმ 4 სიმბოლოს."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"პაროლი არასწორია, ის უნდა შეიცავდეს 4-8 სიმბოლოს, მინიმუმ 1 ციფრს, 1 ასოს და უნდა იყოს შორისის გარეშე."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">უნდა შეიცავდეს მინიმუმ <xliff:g id="COUNT">%d</xliff:g> ასოს</item>
       <item quantity="one">უნდა შეიცავდეს მინიმუმ 1 ასოს</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"დაყენების დასრულება"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ახლა არა"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"ფუნქცია მიუწვდომელია მანქანის მართვისას."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"მანქანის მართვისას მომხმარებლის დამატება ვერ მოხერხდება."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 28fe99d..c5437fb 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Жұптау сұрауы"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысымен жұптау үшін түртіңіз."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Тілдер"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Дыбыс"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Қоңыраудың дыбыс деңгейі"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Навигациялау дыбысының қаттылығы"</string>
@@ -135,7 +134,7 @@
     <string name="legal_information" msgid="1838443759229784762">"Құқықтық ақпарат"</string>
     <string name="contributors_title" msgid="7698463793409916113">"Үлес қосушылар"</string>
     <string name="manual" msgid="4819839169843240804">"Қолмен"</string>
-    <string name="regulatory_labels" msgid="3165587388499646779">"Сертификаттық таңбалар"</string>
+    <string name="regulatory_labels" msgid="3165587388499646779">"Нормативтік белгілер"</string>
     <string name="safety_and_regulatory_info" msgid="1204127697132067734">"Қауіпсіздік және нормативтік ережелер"</string>
     <string name="copyright_title" msgid="4220237202917417876">"Авторлық құқық"</string>
     <string name="license_title" msgid="936705938435249965">"Лицензия"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Пайдаланушыны жою"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Жаңа пайдаланушы"</string>
     <string name="user_guest" msgid="3465399481257448601">"Қонақ"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Әкімші"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Әкімші ретінде кірдіңіз"</string>
     <string name="user_switch" msgid="6544839750534690781">"Ауысу"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Сіз (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Аты"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Қайталау"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Өткізіп жіберу"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Экранды құлыптауды орнатыңыз"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PIN кодын таңдаңыз"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Өрнегіңізді жасаңыз"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Құпия сөзді таңдаңыз"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Ағымдағы экран құлпы"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Қауіпсіздік үшін өрнекті орнатыңыз"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Өшіру"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Өшіру"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Бас тарту"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Растау"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Ең кемі 4 таңбадан тұруы керек"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Құпия сөз 4-8 таңбадан тұруы қажет (ішінде кемінде 1 сан болуы керек)"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Кемінде <xliff:g id="COUNT">%d</xliff:g> таңба болуы керек"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN коды кемінде <xliff:g id="COUNT">%d</xliff:g> таңбадан тұруы керек"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Ең көбі <xliff:g id="NUMBER">%d</xliff:g> таңба болуы керек"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Құрылғы әкімшісі жақында пайдаланылған PIN кодын қолдануға рұқсат бермейді"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"АТ әкімшісі оңай PIN кодтарына тыйым салды. Басқа PIN кодын енгізіп көріңіз."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Жарамсыз таңба болмауы керек"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Құпия сөз жарамсыз, ең кемі 4 таңбадан тұруы керек"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Құпия сөз жарамсыз: 4-8 таңбадан тұруы қажет, оның ішінде кемінде 1 сан, 1 әріп болуы керек (бос орынға рұқсат етілмейді)."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Кемінде <xliff:g id="COUNT">%d</xliff:g> әріп болуы керек</item>
       <item quantity="one">Кемінде 1 әріп болуы керек</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"реттеуді аяқтау"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"қазір емес"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Көлік жүргізу кезінде бұл функция жұмыс істемейді."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Көлік жүргізу кезінде пайдаланушыны енгізу мүмкін емес."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 1ffb6c3..160fbe9 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"សំណើ​ផ្គូផ្គង"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"ចុច​ដើម្បី​ផ្គូផ្គង​ជាមួយ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ។"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ភាសា"</string>
     <string name="sound_settings" msgid="3072423952331872246">"សំឡេង"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"កម្រិត​សំឡេង​រោទ៍"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"កម្រិតសំឡេង​ការរុករក"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"លុប​អ្នក​ប្រើប្រាស់"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"អ្នក​ប្រើប្រាស់​ថ្មី"</string>
     <string name="user_guest" msgid="3465399481257448601">"ភ្ញៀវ"</string>
-    <string name="user_admin" msgid="1535484812908584809">"អ្នកគ្រប់គ្រង"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"បាន​ចូលជា​អ្នក​គ្រប់គ្រង"</string>
     <string name="user_switch" msgid="6544839750534690781">"ប្ដូរ"</string>
     <string name="current_user_name" msgid="3813671533249316823">"អ្នក (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"ឈ្មោះ"</string>
@@ -180,7 +177,7 @@
     <string name="users_list_title" msgid="770764290290240909">"អ្នកប្រើប្រាស់"</string>
     <string name="accounts_settings_title" msgid="436190037084293471">"គណនី"</string>
     <string name="user_details_title" msgid="1104762783367701498">"អ្នក​ប្រើប្រាស់"</string>
-    <string name="no_accounts_added" msgid="5148163140691096055">"គ្មាន​គណនីដែលបានបញ្ចូលទេ"</string>
+    <string name="no_accounts_added" msgid="5148163140691096055">"មិន​មាន​ការ​បញ្ចូល​គណនី​ទេ"</string>
     <string name="account_list_title" msgid="7631588514613843065">"គណនី​សម្រាប់ <xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g>"</string>
     <string name="account_details_title" msgid="7529571432258448573">"ព័ត៌មាន​​អំពី​​គណនី"</string>
     <string name="add_account_title" msgid="5988746086885210040">"បញ្ចូលគណនី"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ព្យាយាមម្ដងទៀត"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"រំលង"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"កំណត់​ការចាក់​សោអេក្រង់"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"ជ្រើសរើសកូដ PIN របស់អ្នក"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"ជ្រើស​រើសលំនាំ​របស់​អ្នក"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"ជ្រើសរើស​ពាក្យសម្ងាត់​របស់អ្នក"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"ការចាក់សោអេក្រង់បច្ចុប្បន្ន"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"ដើម្បីសុវត្ថិភាព សូមកំណត់លំនាំ"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"សម្អាត"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"សម្អាត"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"បោះបង់"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"បញ្ជាក់"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"ត្រូវ​មាន​យ៉ាងហោចណាស់ 4 តួ"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"ពាក្យ​សម្ងាត់​ត្រូវ​តែ​មាន​​ចន្លោះពី 4-8 តួ និង​មាន​លេខ​យ៉ាងហោច​ណាស់ 1"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"ត្រូវ​​មាន​យ៉ាង​ហោច​ណាស់ <xliff:g id="COUNT">%d</xliff:g> តួ"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"កូដ PIN ​ត្រូវ​មាន​លេខ​យ៉ាង​ហោច​ណាស់ <xliff:g id="COUNT">%d</xliff:g> ខ្ទង់"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"ត្រូវមានតិចជាង <xliff:g id="NUMBER">%d</xliff:g> តួ"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"អ្នក​គ្រប់គ្រង​ឧបករណ៍​មិន​អនុញ្ញាត​ឱ្យ​ប្រើ​កូដ PIN ​ទើបប្រើ​ហើយ​ទេ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"កូដ PIN លក្ខណៈ​សាមញ្ញ​ត្រូវ​បាន​ទប់ស្កាត់​ដោយ​អ្នក​គ្រប់គ្រង​ព័ត៌មាន​វិទ្យា​របស់អ្នក។ សាកល្បង​ប្រើ​កូដ PIN ផ្សេង​ពី​នេះ។"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"វាមិនអាច​ប្រើតួ​អក្សរដែល​មិន​ត្រឹមត្រូវ​បានទេ។"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"ពាក្យ​សម្ងាត់​មិនត្រឹមត្រូវទេ ត្រូវ​មាន​យ៉ាងហោចណាស់ 4 តួ។"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"ពាក្យសម្ងាត់​មិន​ត្រឹមត្រូវ​ទេ ត្រូវ​តែ​មាន 4-8 តួ ដែល​មាន​លេខយ៉ាង​ហោចណាស់ 1 ខ្ទង់ អក្សរ 1 តួ និងមិនមានដកឃ្លា។"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">ត្រូវ​មាន​អក្សរ​យ៉ាង​ហោច​ណាស់ <xliff:g id="COUNT">%d</xliff:g> តួ</item>
       <item quantity="one">ត្រូវមានអក្សរយ៉ាងហោចណាស់ 1 តួ</item>
@@ -300,6 +295,7 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ចាក​ចេញ​ពីការបង្ហាញ​សាកល្បង"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"បញ្ចប់​ការរៀបចំ"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"កុំ​ទាន់"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"មិនអាច​ប្រើមុខងារ​នេះបានទេ ខណៈពេល​កំពុង​បើកបរ។"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"មិនអាច​បញ្ចូល​អ្នកប្រើប្រាស់​ខណៈពេល​បើកបរ​បានទេ។"</string>
+    <string name="restricted_while_driving" msgid="6217369093121968299">"មិនអាច​ប្រើមុខងារ​បានទេ ខណៈពេល​កំពុង​បើកបរ។"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 990af8c..b4e3c32 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"ಜೋಡಣೆ ವಿನಂತಿ"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಜೊತೆಗೆ ಜೋಡಿ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ಭಾಷೆಗಳು"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ಶಬ್ದ"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"ರಿಂಗ್ ವಾಲ್ಯೂಮ್"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"ನ್ಯಾವಿಗೇಷನ್ ವಾಲ್ಯೂಮ್‌"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ಬಳಕೆದಾರರನ್ನು ಅಳಿಸಿ"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"ಹೊಸ ಬಳಕೆದಾರರು"</string>
     <string name="user_guest" msgid="3465399481257448601">"ಅತಿಥಿ"</string>
-    <string name="user_admin" msgid="1535484812908584809">"ನಿರ್ವಾಹಕ"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"ನಿರ್ವಾಹಕರಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="user_switch" msgid="6544839750534690781">"ಬದಲಿಸಿ"</string>
     <string name="current_user_name" msgid="3813671533249316823">"ನೀವು (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"ಹೆಸರು"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ಮರುಪ್ರಯತ್ನಿಸಿ"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"ಸ್ಕಿಪ್"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"ಪರದೆ ಲಾಕ್ ಹೊಂದಿಸಿ"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"ನಿಮ್ಮ ಪಿನ್‌ ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"ಪ್ಯಾಟರ್ನ್ ಆಯ್ಕೆ ಮಾಡಿ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್‌ ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"ಪ್ರಸ್ತುತ ಸ್ಕ್ರೀನ್ ಲಾಕ್"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"ಸುರಕ್ಷತೆಗಾಗಿ, ಪ್ಯಾಟ್ರನ್ ಹೊಂದಿಸಿ"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"ತೆರವುಗೊಳಿಸಿ"</string>
@@ -232,7 +227,7 @@
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"ಪ್ಯಾಟರ್ನ್‌ ಉಳಿಸುವಲ್ಲಿ ದೋಷ"</string>
     <string name="okay" msgid="4589873324439764349">"ಸರಿ"</string>
     <string name="remove_screen_lock_title" msgid="1234382338764193387">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ತೆಗೆದುಹಾಕಿ"</string>
-    <string name="remove_screen_lock_message" msgid="6675850371585564965">"ಇದು ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಯಾರು ಬೇಕಾದರೂ ಪ್ರವೇಶಿಸಲು ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="remove_screen_lock_message" msgid="6675850371585564965">"ಇದು ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಯಾರಾದರೂ ಪ್ರವೇಶಿಸಲು ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="lock_settings_enter_pin" msgid="1669172111244633904">"ನಿಮ್ಮ ಪಿನ್ ನಮೂದಿಸಿ"</string>
     <string name="lock_settings_enter_password" msgid="2636669926649496367">"ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ನಮೂದಿಸಿ"</string>
     <string name="choose_lock_pin_message" msgid="2963792070267774417">"ಸುರಕ್ಷತೆಗಾಗಿ, ಪಿನ್ ಅನ್ನು ಹೊಂದಿಸಿ"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"ರದ್ದುಮಾಡಿ"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"ದೃಢೀಕರಿಸಿ"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"ಕನಿಷ್ಠ 4 ಅಕ್ಷರಗಳು ಇರಬೇಕು"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"ಪಾಸ್‌ವರ್ಡ್‌ ಕನಿಷ್ಠ 1 ಸಂಖ್ಯೆಯೊಂದಿಗೆ 4 ರಿಂದ 8 ಅಕ್ಷರಗಳ ನಡುವೆ ಇರಬೇಕು"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"ಕನಿಷ್ಠ <xliff:g id="COUNT">%d</xliff:g> ಅಕ್ಷರಗಳನ್ನು ಹೊಂದಿರಬೇಕು"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"ಪಿನ್ ಕನಿಷ್ಠ <xliff:g id="COUNT">%d</xliff:g> ಅಂಕಿಗಳನ್ನು ಹೊಂದಿರಬೇಕು"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> ಗಿಂತ ಕಡಿಮೆ ಅಕ್ಷರಗಳನ್ನು ಹೊಂದಿರಬೇಕು"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ಇತ್ತೀಚಿನ ಪಿನ್ ಬಳಸಲು ಸಾಧನದ ನಿರ್ವಾಹಕರು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ಸರಳವಾದ ಪಿನ್‌ಗಳನ್ನು ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರಿಂದ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. ಬೇರೆಯ ಪಿನ್ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ಇದು ಅಮಾನ್ಯ ಅಕ್ಷರವನ್ನು ಒಳಗೊಂಡಿರಬಾರದು."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"ಪಾಸ್‌ವರ್ಡ್‌ ಅಮಾನ್ಯವಾಗಿದೆ, ಕನಿಷ್ಠ 4 ಅಕ್ಷರಗಳು ಇರಬೇಕು."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"ಪಾಸ್‌ವರ್ಡ್‌ ಅಮಾನ್ಯವಾಗಿದೆ, 4-8 ಅಕ್ಷರಗಳು ಇರಬೇಕು, ಕನಿಷ್ಠ 1 ಅಂಕಿಯ, 1 ಅಕ್ಷರ, ವೈಟ್‌ಸ್ಪೇಸ್‌ ಇರಬಾರದು."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">ಕನಿಷ್ಠ <xliff:g id="COUNT">%d</xliff:g> ಅಕ್ಷರಗಳನ್ನು ಹೊಂದಿರಬೇಕು</item>
       <item quantity="other">ಕನಿಷ್ಠ <xliff:g id="COUNT">%d</xliff:g> ಅಕ್ಷರಗಳನ್ನು ಹೊಂದಿರಬೇಕು</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ಡೆಮೊದಿಂದ ನಿರ್ಗಮಿಸಿ"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ಸೆಟಪ್ ಪೂರ್ಣಗೊಳಿಸಿ"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ಈಗಲೇ ಬೇಡ"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ಡ್ರೈವ್ ಮಾಡುವಾಗ ಈ ವೈಶಿಷ್ಟ್ಯ ಲಭ್ಯವಿಲ್ಲ."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ಚಾಲನೆ ಮಾಡುವಾಗ ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 85dea65..ba91399 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"페어링 요청"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>에 페어링하려면 탭하세요."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"언어"</string>
     <string name="sound_settings" msgid="3072423952331872246">"소리"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"벨소리 볼륨"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"내비게이션 볼륨"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"사용자 삭제"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"새 사용자"</string>
     <string name="user_guest" msgid="3465399481257448601">"게스트"</string>
-    <string name="user_admin" msgid="1535484812908584809">"관리자"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"관리자로 로그인했습니다."</string>
     <string name="user_switch" msgid="6544839750534690781">"전환"</string>
     <string name="current_user_name" msgid="3813671533249316823">"사용자(%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"이름"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"다시 시도"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"건너뛰기"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"화면 잠금 설정"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PIN 선택"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"패턴 선택"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"비밀번호 선택"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"현재 화면 잠금"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"보안을 위해 패턴을 설정하세요."</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"삭제"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"삭제"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"취소"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"확인"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"4자 이상이어야 합니다."</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"비밀번호는 4~8자이며 숫자가 1개 이상 포함되어야 합니다."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"<xliff:g id="COUNT">%d</xliff:g>자 이상이어야 합니다."</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN은 <xliff:g id="COUNT">%d</xliff:g>자리 이상이어야 합니다."</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g>자 미만이어야 합니다."</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"기기 관리자가 최근 PIN 사용을 허용하지 않습니다."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"IT 관리자가 일반적으로 사용되는 PIN을 허용하지 않습니다. 다른 PIN으로 시도해 보세요."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"잘못된 문자를 포함할 수 없습니다."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"비밀번호는 4자 이상이어야 합니다."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"비밀번호가 잘못되었습니다. 비밀번호는 숫자 1개와 문자 1개 이상이 포함된 4~8자여야 하며 공백이 없어야 합니다."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">문자를 <xliff:g id="COUNT">%d</xliff:g>개 이상 포함해야 합니다.</item>
       <item quantity="one">문자를 1개 이상 포함해야 합니다.</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"설정 완료"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"나중에"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"운전 중 기능 사용 불가"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"운전 중에는 사용자를 추가할 수 없습니다."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index e254da1..9cd78de 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Жупташтыруу өтүнүчү"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> менен жупташуу үчүн таптап коюңуз."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Тилдер"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Үн"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Шыңгырдын үнү"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Чабыттоонун үнү"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Колдонуучуну жок кылуу"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Жаңы колдонуучу"</string>
     <string name="user_guest" msgid="3465399481257448601">"Конок"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Админ"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Администратор катары кирдиңиз"</string>
     <string name="user_switch" msgid="6544839750534690781">"Которуштуруу"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Сиз (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Колдонуучунун аты"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Кайталоо"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Өткөрүп жиберүү"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Экран кулпусун коюп алыңыз"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PIN кодуңузду тандаңыз"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Графикалык ачкычты тандаңыз"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Сырсөзүңүздү тандаңыз"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Учурдагы экран кулпусу"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Коопсуздук үчүн графикалык ачкыч коюңуз"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Тазалоо"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Тазалоо"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Жокко чыгаруу"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Ырастоо"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Кеминде 4 белгиден турушу керек"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Сырсөз арасында кеминде 1 сан кошулган 4-8 белгиден турушу керек"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Кеминде <xliff:g id="COUNT">%d</xliff:g> белгиден турушу керек"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN код кеминде <xliff:g id="COUNT">%d</xliff:g> сандан турушу керек"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> белгиден ашпашы керек"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Түзмөктүн администратору акыркы PIN кодду колдонууга тыюу салган"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Жөнөкөй PIN-коддорду коюу IT администраторуңуз тарабынан бөгөттөлгөн. Татаалыраак PIN-кодду коюп көрүңүз."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Бул жерде жараксыз белги камтылбашы керек."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Сырсөз жараксыз, кеминде 4 белгиден турушу керек."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Сырсөз жараксыз. Ал арасында кеминде 1 сан, 1 тамга кошулган 4-8 белгиден туруп, боштугу жок жазылышы керек."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Кеминде <xliff:g id="COUNT">%d</xliff:g> тамга болушу керек</item>
       <item quantity="one">Кеминде 1 тамга болушу керек</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"жөндөөнү бүтүрүү"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"азыр эмес"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Унаа айдаганда бул функция жеткиликтүү эмес."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Унаа айдап баратканда колдонуучуну кошууга болбойт."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 93c3706..76770ce 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"ຄຳຂໍຈັບຄູ່"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"ແຕະເພື່ອຈັບຄູ່ກັບ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ພາສາ"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ສຽງ"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"ລະດັບສຽງໂທລະສັບ"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"ລະດັບສຽງການນຳທາງ"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ລຶບຜູ້ໃຊ້"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"ຜູ້ໃຊ້ໃໝ່"</string>
     <string name="user_guest" msgid="3465399481257448601">"ແຂກ"</string>
-    <string name="user_admin" msgid="1535484812908584809">"ຜູ້ເບິ່ງແຍງລະບົບ"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"ເຂົ້າສູ່ລະບົບເປັນຜູ້ເບິ່ງແຍງລະບົບ"</string>
     <string name="user_switch" msgid="6544839750534690781">"ສະຫຼັບ"</string>
     <string name="current_user_name" msgid="3813671533249316823">"ທ່ານ (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"ຊື່"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ລອງໃໝ່"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"ຂ້າມ"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"ຕັ້ງຄ່າໜ້າຈໍລັອກ"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"ເລືອກ PIN ຂອງທ່ານ"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"ເລືອກຮູບແບບຂອງທ່ານ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"ເລືອກລະຫັດຜ່ານຂອງທ່ານ"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"ການລັອກໜ້າຈໍປັດຈຸບັນ"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"ເພື່ອຄວາມປອດໄພ, ກະລຸນາຕັ້ງຮູບແບບປົດລັອກ"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"ລຶບ"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"ລຶບ"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"ຍົກເລີກ"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"ຢືນຢັນ"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"ຕ້ອງມີຢ່າງໜ້ອຍ 4 ຕົວອັກສອນ"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"ລະຫັດຜ່ານຕ້ອງມີອັກຂະລະ 4-8 ຕົວ ໂດຍມີຢ່າງໜ້ອຍ 1 ຕົວເລກ"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"ຕ້ອງມີອັກຂະລະຢ່າງໜ້ອຍ <xliff:g id="COUNT">%d</xliff:g> ຕົວ"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN ຕ້ອງມີຢ່າງໜ້ອຍ <xliff:g id="COUNT">%d</xliff:g> ຕົວເລກ"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"ຕ້ອງມີອັກຂະລະໜ້ອຍກວ່າ <xliff:g id="NUMBER">%d</xliff:g> ຕົວ"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ຜູ້ເບິ່ງແຍງລະບົບອຸປະກອນບໍ່ອະນຸຍາດໃຫ້ໃຊ້ລະຫັດ PIN ເມື່ອບໍ່ດົນມານີ້"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"PIN ທີ່ມັກໃຊ້ທົ່ວໄປຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ. ກະລຸນາລອງໃຊ້ PIN ອື່ນ."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ນີ້ບໍ່ສາມາດຮວມມີອັກຂະລະທີ່ບໍ່ຖືກຕ້ອງໄດ້."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"ລະຫັດຜ່ານໃຊ້ບໍ່ໄດ້, ຕ້ອງມີຢ່າງໜ້ອຍ 4 ຕົວອັກສອນ."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ, ຕ້ອງມີອັກຂະລະ 4-8 ຕົວ, ໂດຍມີຢ່າງໜ້ອຍ 1 ຕົວເລກ, 1 ຕົວອັກສອນ, ບໍ່ມີຍະຫວ່າງ."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">ຕ້ອງມີຕົວອັກສອນຢ່າງໜ້ອຍ <xliff:g id="COUNT">%d</xliff:g> ຕົວ</item>
       <item quantity="one">ຕ້ອງມີຕົວອັກສອນຢ່າງໜ້ອຍ 1 ຕົວ</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ສຳເລັດການຕັ້ງຄ່າ"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ບໍ່ແມ່ນຕອນນີ້"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"ຄຸນສົມບັດບໍ່ສາມາດໃຊ້ໄດ້ໃນເວລາຂັບລົດ."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ບໍ່ສາມາດເພີ່ມຜູ້ໃຊ້ໃນຂະນະທີ່ຂັບລົດໄດ້."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index d59b035..c036379 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Susiejimo užklausa"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Palieskite, kad susietumėte su „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Kalbos"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Garsas"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Skambučio garsumas"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigacijos garsumas"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Naudotojo ištrynimas"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Naujas naudotojas"</string>
     <string name="user_guest" msgid="3465399481257448601">"Svečias"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administratorius"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Esate prisij. kaip administrator."</string>
     <string name="user_switch" msgid="6544839750534690781">"Jungiklis"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Jūs (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Vardas"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Bandyti dar kartą"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Praleisti"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ekrano užrakto nustatymas"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Pasirinkite PIN kodą"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Pasirinkite piešinį"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Pasirinkite slaptažodį"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Esamas ekrano užraktas"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Nustatykite saugumo atrakinimo piešinį"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Išvalyti"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Išvalyti"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Atšaukti"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Patvirtinti"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Turi būti bent 4 simboliai"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Slaptažodį turi sudaryti 4–8 simboliai, bent 1 iš jų – skaitmuo"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Turi būti bent <xliff:g id="COUNT">%d</xliff:g> simbol."</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN kodą turi sudaryti bent <xliff:g id="COUNT">%d</xliff:g> skaitm."</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Turi būti mažiau nei <xliff:g id="NUMBER">%d</xliff:g> simbol."</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Įrenginio administratorius neleidžia naudoti pastarojo PIN kodo"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Dažnai naudojamus PIN kodus užblokavo IT administratorius. Bandykite naudoti kitą PIN kodą."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Čia negali būti netinkamų simbolių."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Slaptažodis netinkamas, jį turi sudaryti bent 4 simboliai."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Slaptažodis netinkamas; jis turi būti sudarytas iš 4–8 simbolių, iš kurių bent 1 turi būti skaitmuo, 1 raidė, negali būti matomų tarpų."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Turi būti bent <xliff:g id="COUNT">%d</xliff:g> raidė</item>
       <item quantity="few">Turi būti bent <xliff:g id="COUNT">%d</xliff:g> raidės</item>
@@ -315,5 +310,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"užbaigti sąranką"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ne dabar"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funkcija nepasiekiama vairuojant."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Negalima pridėti naudotojo vairuojant."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 4d74066..0b73a53 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Pieprasījums savienošanai pārī"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Pieskarieties, lai izveidotu savienojumu pārī ar ierīci “<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Valodas"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Skaņa"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Zvana skaļums"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigācijas skaļums"</string>
@@ -171,8 +170,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Dzēst lietotāju"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Jauns lietotājs"</string>
     <string name="user_guest" msgid="3465399481257448601">"Viesis"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrators"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Pierakstījies kā administrators"</string>
     <string name="user_switch" msgid="6544839750534690781">"Pārslēgt"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Jūs (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Vārds"</string>
@@ -215,9 +212,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Mēģināt vēlreiz"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Izlaist"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ekrāna bloķēšanas iestatīšana"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Izvēlieties PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Kombinācijas izvēle"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Izvēlieties paroli"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Pašreizējais ekrāna bloķēšanas veids"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Drošības nolūkos iestatiet kombināciju."</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Notīrīt"</string>
@@ -250,7 +245,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Notīrīt"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Atcelt"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Apstiprināt"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Jābūt vismaz 4 rakstzīmēm"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Parolē ir jābūt 4–8 rakstzīmēm un vismaz 1 ciparam."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> rakstzīmes."</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN kodā ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> cipari."</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Ir jābūt mazāk nekā <xliff:g id="NUMBER">%d</xliff:g> rakstzīmēm."</string>
@@ -259,7 +254,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Ierīces administrators neļauj izmantot nesen izveidotu PIN."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Jūsu IT administrators ir bloķējis pārāk vienkāršus PIN. Izmēģiniet sarežģītāku PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Nedrīkst ietvert nederīgu rakstzīmi."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Parole nav derīga; tajā jābūt vismaz 4 rakstzīmēm."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Parole nav derīga. Tajā ir jābūt 4–8 rakstzīmēm, jāietver vismaz 1 cipars un 1 burts, un tajā nedrīkst būt baltstarpas."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="zero">Ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> burti.</item>
       <item quantity="one">Ir jāietver vismaz <xliff:g id="COUNT">%d</xliff:g> burts.</item>
@@ -308,5 +303,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"pabeigt iestatīšanu"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"vēlāk"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funkcija nav pieejama braukšanas laikā."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Nevar pievienot lietotāju braukšanas laikā."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 7e67cff..817e10e 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Барање за спарување"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Допрете за да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Јазици"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Звук"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Јачина на звук на ѕвонење"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Јачина на звук на навигацијата"</string>
@@ -142,7 +141,7 @@
     <string name="terms_title" msgid="5201471373602628765">"Правила и услови"</string>
     <string name="webview_license_title" msgid="2531829466541104826">"Системска лиценца за WebView"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"Тапети"</string>
-    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Извор на сателитски снимки:\n©2014 CNES/Astrium, DigitalGlobe, Bluesky"</string>
+    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Даватели на сателитски снимки:\n©2014 CNES/Astrium, DigitalGlobe, Bluesky"</string>
     <string name="settings_license_activity_title" msgid="8499293744313077709">"Лиценци на трети лица"</string>
     <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"Постои проблем при вчитување на лиценците."</string>
     <string name="settings_license_activity_loading" msgid="6163263123009681841">"Се вчитува…"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Избриши корисник"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Нов корисник"</string>
     <string name="user_guest" msgid="3465399481257448601">"Гостин"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Администратор"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Најавени сте како администратор"</string>
     <string name="user_switch" msgid="6544839750534690781">"Префрли"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Вие (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Име"</string>
@@ -214,16 +211,14 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Повторно"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Прескокни"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Поставете заклучување екран"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Изберете PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Изберете шема"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Изберете лозинка"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Тековно заклучување на екранот"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"За безбедност, поставете шема"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Исчисти"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"Откажи"</string>
     <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"Вашата нова шема за отклучување"</string>
     <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"Нацртајте ја шемата за отклучување"</string>
-    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Отпуштете го прстот кога ќе завршите"</string>
+    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Отпуштете го прстот кога е готово"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"Шемата е снимена"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"Употреби ја шемата повторно за потврда"</string>
     <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Поврзете барем 4 точки. Обидете се пак."</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Исчисти"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Откажи"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Потврди"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Мора да има најмалку 4 знаци"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Лозинката мора да има 4 - 8 знаци и барем 1 цифра"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Мора да има најмалку <xliff:g id="COUNT">%d</xliff:g> знаци"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN-от мора да има најмалку <xliff:g id="COUNT">%d</xliff:g> цифри"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Мора да содржи помалку од <xliff:g id="NUMBER">%d</xliff:g> знаци"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Администраторот на уредот не дозволува користење на неодамнешен PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Обичните PIN-кодови се блокирани од вашиот IT-администратор. Обидете се со друг PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Лозинката не може да содржи неважечки знак."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Лозинката е неважечка, мора да има најмалку 4 знаци."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Лозинката е неважечка. Мора да има од 4 до 8 знаци, да содржи барем една цифра, 1 буква и да нема празни места."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Мора да содржи најмалку <xliff:g id="COUNT">%d</xliff:g> буква</item>
       <item quantity="other">Мора да содржи најмалку <xliff:g id="COUNT">%d</xliff:g> букви</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"заврши со поставување"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"не сега"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Функцијата не е достапна при возење."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Не може да се додаде корисник додека возите."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index f69d432..0ce9281 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"ജോടിയാക്കൽ അഭ്യർത്ഥന"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> എന്ന ഉപകരണവുമായി ജോടിയാക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ഭാഷകൾ"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ശബ്‌ദം"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"റിംഗ് വോളിയം"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"നാവിഗേഷൻ വോളിയം"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ഉപയോക്താവിനെ ഇല്ലാതാക്കുക"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"പുതിയ ഉപയോക്താവ്"</string>
     <string name="user_guest" msgid="3465399481257448601">"അതിഥി"</string>
-    <string name="user_admin" msgid="1535484812908584809">"അഡ്‌മിന്‍"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"അഡ്‌മിൻ ആയി സൈൻ ഇൻ ചെയ്‌തു"</string>
     <string name="user_switch" msgid="6544839750534690781">"മാറുക"</string>
     <string name="current_user_name" msgid="3813671533249316823">"നിങ്ങൾ (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"പേര്"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"ഒഴിവാക്കുക"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"ഒരു സ്‌ക്രീൻ ലോക്ക് സജ്ജീകരിക്കുക"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"പിൻ തിരഞ്ഞെടുക്കുക"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"പാറ്റേൺ തിരഞ്ഞെടുക്കൂ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"നിങ്ങളുടെ പാസ്‌വേഡ് തിരഞ്ഞെടുക്കുക"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"നിലവിലെ സ്‌ക്രീൻ ലോക്ക്"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"സുരക്ഷയ്‌ക്കായി, പാറ്റേൺ സജ്ജീകരിക്കുക"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"മായ്ക്കുക"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"മായ്ക്കുക"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"റദ്ദാക്കുക"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"സ്ഥിരീകരിക്കുക"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"കുറഞ്ഞത് 4 പ്രതീകങ്ങളെങ്കിലും ഉണ്ടായിരിക്കണം"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"പാസ്‌വേഡ് 4-8 പ്രതീകങ്ങൾക്ക് ഇടയിലായിരിക്കണം, ചുരുങ്ങിയത് ഒരു അക്കമെങ്കിലും ഉണ്ടായിരിക്കണം"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"<xliff:g id="COUNT">%d</xliff:g> പ്രതീകങ്ങളെങ്കിലും അടങ്ങിയിരിക്കണം"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"പിന്നിൽ <xliff:g id="COUNT">%d</xliff:g> അക്കങ്ങളെങ്കിലും ഉണ്ടാകണം"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> പ്രതീകങ്ങളേക്കാൾ കുറവായിരിക്കണം"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ഉപകരണ അഡ്‌മിൻ സമീപകാലത്തുള്ള പിൻ ഉപയോഗിക്കുന്നത് അനുവദിക്കുന്നില്ല"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"നിങ്ങളുടെ ഐടി അഡ്‌മിൻ സാധാരണ പിന്നുകൾ ബ്ലോക്ക് ചെയ്‌തിട്ടുണ്ട്. മറ്റൊരു പിൻ പരീക്ഷിക്കുക."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ഇതിൽ അസാധുവായൊരു പ്രതീകം ഉണ്ടായിരിക്കാൻ പാടില്ല."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"പാസ്‌‌വേഡ് അസാധുവാണ്, കുറഞ്ഞത് 4 പ്രതീകങ്ങളെങ്കിലും ഉണ്ടായിരിക്കണം."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"അസാധുവായ പാസ്‌വേഡ്, 4-8 പ്രതീകങ്ങൾ വരെ ഉണ്ടായിരിക്കണം, ചുരുങ്ങിയത് ഒരു അക്കവും ഒരു അക്ഷരവും അടങ്ങിയിരിക്കണം, വൈറ്റ് സ്പേസ് ഉണ്ടാകരുത്."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">കുറഞ്ഞത് <xliff:g id="COUNT">%d</xliff:g> അക്ഷരങ്ങളെങ്കിലും അടങ്ങിയിരിക്കണം</item>
       <item quantity="one">കുറഞ്ഞത് ഒരു അക്ഷരമെങ്കിലും അടങ്ങിയിരിക്കണം</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ഡെമോയിൽ നിന്ന് പുറത്തുകടക്കുക"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"സജ്ജീകരണം പൂർത്തിയാക്കുക"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ഇപ്പോൾ വേണ്ട"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ഡ്രൈവ് ചെയ്യുമ്പോൾ ഫീച്ചർ ലഭ്യമല്ല."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ഡ്രൈവ് ചെയ്യുമ്പോൾ ഉപയോക്താവിനെ ചേർക്കാനാവില്ല."</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index b34bef6..1375fe7 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Хослуулах хүсэлт"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>-тай хослуулахын тулд товшино уу."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Хэл"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Дуу"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Хонх дуугаргах түвшин"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Шилжүүлгийн түвшин"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Хэрэглэгчийг устгах"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Шинэ хэрэглэгч"</string>
     <string name="user_guest" msgid="3465399481257448601">"Зочин"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Админ"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Админаар нэвтэрсэн"</string>
     <string name="user_switch" msgid="6544839750534690781">"Сэлгэх"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Та (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Нэр"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Дахин оролдох"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Алгасах"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Дэлгэцийн түгжээ тохируулах"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Пин кодоо сонгоно уу"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Хээгээ сонгоно уу"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Нууц үгээ сонгоно уу"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Одоогийн дэлгэцийн түгжээ"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Аюулгүй байдлын үүднээс загвар тохируулах"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Устгах"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Устгах"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Цуцлах"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Баталгаажуулах"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Хамгийн багадаа 4 тэмдэгттэй байх ёстой"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Нууц үг нь хамгийн багадаа 1 тоо агуулсан 4-8 тэмдэгттэй байх ёстой"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Хамгийн багадаа <xliff:g id="COUNT">%d</xliff:g> тэмдэгттэй байх ёстой"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"ПИН хамгийн багадаа <xliff:g id="COUNT">%d</xliff:g> цифртэй байх ёстой"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g>-с цөөн тэмдэгттэй байх ёстой"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Төхөөрөмжийн админ саяхны ПИН-г ашиглахыг зөвшөөрдөггүй"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Таны IT админ түгээмэл ПИН-г блоклосон байна. Өөр ПИН оруулна уу."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Үүнд буруу тэмдэгт агуулах боломжгүй."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Нууц үг буруу байна. Хамгийн багадаа 4 тэмдэгттэй байх ёстой."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Нууц үг буруу байна. Үүнд хамгийн багадаа 1 цифр, 1 үсэг бүхий 4-8 тэмдэгт агуулсан байх ёстой бөгөөд хоосон зай агуулж болохгүй."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Хамгийн багадаа <xliff:g id="COUNT">%d</xliff:g> үсэг агуулах ёстой</item>
       <item quantity="one">Хамгийн багадаа 1 үсэг агуулах ёстой</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"Туршилтаас гарах"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"тохиргоог дуусгах"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"одоо биш"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"Энэ онцлог жолоо барьж байх үед боломжгүй."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Жолоо барьж байх үед хэрэглэгч нэмэх боломжгүй."</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index e1bca11..0c81dec 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -55,11 +55,11 @@
     <item msgid="3859756017461098953">"उत्तम"</item>
     <item msgid="1521103743353335724">"उत्कृष्ट"</item>
   </string-array>
-    <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"ब्लूटूथ"</string>
+    <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"ब्लुटूथ"</string>
     <string name="bluetooth_quick_toggle_summary" msgid="4390699431893305353">"ब्लूटूथ चालू करा"</string>
-    <string name="bluetooth_settings" msgid="3878243366013638982">"ब्लूटूथ"</string>
+    <string name="bluetooth_settings" msgid="3878243366013638982">"ब्लुटूथ"</string>
     <string name="bluetooth_disabled" msgid="4187409401590350572">"ब्लूटूथ बंद केले"</string>
-    <string name="bluetooth_settings_title" msgid="3794688574569688649">"ब्लूटूथ"</string>
+    <string name="bluetooth_settings_title" msgid="3794688574569688649">"ब्लुटूथ"</string>
     <string name="bluetooth_settings_summary" msgid="4023303473646769835">"कनेक्शन व्यवस्थापित करा, डिव्हाइस नाव आणि शोधसुलभता सेट करा"</string>
     <string name="bluetooth_talkback_computer" msgid="5223330129934365312">"कॉंप्युटर"</string>
     <string name="bluetooth_talkback_headset" msgid="6155254514321149935">"हेडसेट"</string>
@@ -67,7 +67,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"इमेज"</string>
     <string name="bluetooth_talkback_headphone" msgid="5362155791551671490">"हेडफोन"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="868933277567862622">"इनपुट पेरिफरल"</string>
-    <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"ब्लूटूथ"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"ब्लुटूथ"</string>
     <string name="bluetooth_preference_paired_devices" msgid="5875643105380630583">"पेअर केलेले डिव्हाइस"</string>
     <string name="bluetooth_preference_found_devices" msgid="125155123214560511">"उपलब्ध डिव्हाइस"</string>
     <string name="bluetooth_preference_no_paired_devices" msgid="483742146117390001">"कोणतेही पेअर केलेले डिव्हाइस नाहीत"</string>
@@ -75,10 +75,10 @@
     <string name="bluetooth_preference_paired_dialog_title" msgid="2470829827455850904">"पेअर केलेले डिव्हाइस"</string>
     <string name="bluetooth_preference_paired_dialog_name_label" msgid="3528740139365123415">"नाव"</string>
     <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"यासाठी वापरा"</string>
-    <string name="wifi_ssid_hint" msgid="4155050863239489553">"ब्लूटूथ डिव्हाइसचे नाव बदला"</string>
+    <string name="wifi_ssid_hint" msgid="4155050863239489553">"ब्लुटूथ डिव्हाइसचे नाव बदला"</string>
     <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"ब्लूटूथ पेअरींग विनंती"</string>
     <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"पेअर करा आणि कनेक्ट करा"</string>
-    <string name="bluetooth" msgid="5235115159234688629">"ब्लूटूथ"</string>
+    <string name="bluetooth" msgid="5235115159234688629">"ब्लुटूथ"</string>
     <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"ब्लूटूथ पेअरींग कोड"</string>
     <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"पिन मध्ये अक्षरे किंवा चिन्हे आहेत"</string>
     <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"पेअरींग कोड टाइप करा नंतर परत या किंवा Enter दाबा"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"पेअरींग विनंती"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> सह जोडण्यासाठी टॅप करा."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"भाषा"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ध्वनी"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"रिंग आवाज"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"नेव्हिगेशन आवाज"</string>
@@ -100,7 +99,7 @@
     <string name="media_volume_title" msgid="6697416686272606865">"मीडिया"</string>
     <string name="media_volume_summary" msgid="2961762827637127239">"संगीत आणि व्हिडिओंसाठी व्हॉल्यूम सेट करा"</string>
     <string name="alarm_volume_title" msgid="840384014895796587">"अलार्म"</string>
-    <string name="applications_settings" msgid="794261395191035632">"ॲप माहिती"</string>
+    <string name="applications_settings" msgid="794261395191035632">"अॅप माहिती"</string>
     <string name="disable_text" msgid="4358165448648990820">"बंद करा"</string>
     <string name="enable_text" msgid="1794971777861881238">"चालू करा"</string>
     <string name="permissions_label" msgid="2701446753515612685">"परवानग्या"</string>
@@ -108,12 +107,12 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"कोणत्याही परवानग्यांना मंजूरी दिली नाही"</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"कोणत्याही परवानग्यांची विनंती केली नाही"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"डेटा वापर"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"ॲप डेटा वापर"</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"अॅप डेटा वापर"</string>
     <string name="force_stop" msgid="2153183697014720520">"सक्तीने थांबवा"</string>
     <string name="computing_size" msgid="5791407621793083965">"गणना करत आहे…"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
+      <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> अतिरिक्त परवानगी</item>
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> अतिरिक्त परवानग्या</item>
-      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> अतिरिक्त परवानगी</item>
     </plurals>
     <string name="system_setting_title" msgid="6864599341809463440">"सिस्टम"</string>
     <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"सिस्टम अपडेट"</string>
@@ -133,7 +132,7 @@
     <string name="about_summary" msgid="5374623866267691206">"Android <xliff:g id="VERSION">%1$s</xliff:g>"</string>
     <string name="about_settings_summary" msgid="7975072809083281401">"कायदेशीर माहिती, स्थिती, सॉफ्टवेअर आवृत्ती पहा"</string>
     <string name="legal_information" msgid="1838443759229784762">"कायदेशीर माहिती"</string>
-    <string name="contributors_title" msgid="7698463793409916113">"कंट्रिब्युटर्स"</string>
+    <string name="contributors_title" msgid="7698463793409916113">"योगदानकर्ते"</string>
     <string name="manual" msgid="4819839169843240804">"मॅन्युअल"</string>
     <string name="regulatory_labels" msgid="3165587388499646779">"नियामक लेबले"</string>
     <string name="safety_and_regulatory_info" msgid="1204127697132067734">"सुरक्षा आणि नियामक मॅन्युअल"</string>
@@ -153,7 +152,7 @@
     <string name="date_time_auto_summary" msgid="3311706425095342759">"नेटवर्कने-प्रदान केलेली वेळ वापरा"</string>
     <string name="zone_auto" msgid="3701878581920206160">"आपोआप टाइम झोन"</string>
     <string name="zone_auto_summary" msgid="4345856882906981864">"नेटवर्कने-प्रदान केलेला टाइम झोन वापरा"</string>
-    <string name="date_time_24hour_title" msgid="3025576547136168692">"२४‑तास फॉरमॅट"</string>
+    <string name="date_time_24hour_title" msgid="3025576547136168692">"24‑तास फॉरमॅट"</string>
     <string name="date_time_24hour" msgid="1137618702556486913">"२४-तास फॉरमॅट वापरा"</string>
     <string name="date_time_set_time_title" msgid="5884883050656937853">"वेळ"</string>
     <string name="date_time_set_time" msgid="6449555153906058248">"वेळ सेट करा"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"वापरकर्ता हटवा"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"नवीन वापरकर्ता"</string>
     <string name="user_guest" msgid="3465399481257448601">"अतिथी"</string>
-    <string name="user_admin" msgid="1535484812908584809">"प्रशासक"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"प्रशासक म्हणून साइन इन केले आहे"</string>
     <string name="user_switch" msgid="6544839750534690781">"स्विच"</string>
     <string name="current_user_name" msgid="3813671533249316823">"तुम्ही (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"नाव"</string>
@@ -189,7 +186,7 @@
     <string name="remove_account_title" msgid="8840386525787836381">"खाते काढा"</string>
     <string name="really_remove_account_title" msgid="3555164432587924900">"खाते काढायचे?"</string>
     <string name="really_remove_account_message" msgid="4296769280849579900">"हे खाते काढल्याने त्याचे सर्व संदेश, संपर्क आणि डिव्हाइसवरील इतर डेटा हटवला जाईल!"</string>
-    <string name="remove_account_failed" msgid="7472511529086294087">"या बदलास तुमच्या प्रशासकाची अनुमती नाही"</string>
+    <string name="remove_account_failed" msgid="7472511529086294087">"या बदलास तुमच्या प्रशासकाने अनुमती नाही"</string>
     <string name="really_remove_user_title" msgid="4990029019291756762">"हा वापरकर्ता काढायचा?"</string>
     <string name="really_remove_user_message" msgid="3828090902833944533">"सर्व अॅप्स आणि डेटा हटवला जाईल."</string>
     <string name="remove_user_error_title" msgid="2038275458657689420">"वापरकर्ता काढता आला नाही."</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"पुन्हा प्रयत्न करा"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"वगळा"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"स्क्रीन लॉक सेट करा"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"तुमचा पिन निवडा"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"तुमचा पॅटर्न निवडा"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"तुमचा पासवर्ड निवडा"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"सध्‍याचा स्क्रीन लॉक"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"सुरक्षिततेसाठी, एक पॅटर्न सेट करा"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"साफ करा"</string>
@@ -225,8 +220,8 @@
     <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"अनलॉक पॅटर्न रेखाटा"</string>
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"पूर्ण झाल्यावर बोट काढा"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"पॅटर्न रेकॉर्ड झाला"</string>
-    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"निश्चित करण्यासाठी पुन्हा रेखांकित करा"</string>
-    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"किमान ४ बिंदू जोडा. पुन्हा प्रयत्न करा."</string>
+    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"पुष्टी करण्यासाठी पुन्हा रेखांकित करा"</string>
+    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"किमान ४ बिंदू कनेक्ट करा. पुन्हा प्रयत्न करा."</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"चुकीचा पॅटर्न"</string>
     <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"अनलॉक पॅटर्न कसा काढावा"</string>
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"पॅटर्न सेव्ह करताना एरर आली"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"साफ करा"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"रद्द करा"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"खात्री करा"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"किमान 4 वर्ण असणे आवश्यक आहे"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"किमान १ नंबरसह पासवर्ड किमान ४-८ वर्णांंदरम्यानचा असणे आवश्यक आहे"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"किमान <xliff:g id="COUNT">%d</xliff:g> वर्ण असणे आवश्यक आहे"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"पिन किमान <xliff:g id="COUNT">%d</xliff:g> अंकी असणे आवश्यक आहे"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> वर्णांपेक्षा कमी असणे आवश्यक आहे"</string>
@@ -258,30 +253,30 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"डिव्हाइस प्रशासक अलीकडील पिन वापरण्याची अनुमती देत नाही"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"सामान्यत: वापरले जाणारे पिन तुमच्या IT प्रशासनाने ब्लॉक केलेले आहेत. दुसरा एखादा पिन वापरून पाहा."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"यामध्ये चुकीच्‍या वर्णांचा समावेश असू शकत नाही."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"पासवर्ड चुकीचा आहे, किमान 4 वर्ण असणे आवश्यक आहे."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"पासवर्ड चुकीचा आहे, ४-८ वर्ण, किमान १ अंक, १ अक्षर असणे आवश्‍यक आहे, व्‍हाइटस्‍पेस असू नये."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
+      <item quantity="one">किमान <xliff:g id="COUNT">%d</xliff:g> अक्षर असणे आवश्यक आहे</item>
       <item quantity="other">किमान <xliff:g id="COUNT">%d</xliff:g> अक्षरे असणे आवश्यक आहे</item>
-      <item quantity="one">किमान एक अक्षर असणे आवश्यक आहे</item>
     </plurals>
     <plurals name="lockpassword_password_requires_lowercase" formatted="false" msgid="2267487180744744833">
+      <item quantity="one">किमान <xliff:g id="COUNT">%d</xliff:g> लोअरकेस अक्षर असणे आवश्यक आहे</item>
       <item quantity="other">किमान <xliff:g id="COUNT">%d</xliff:g> लोअरकेस अक्षरे असणे आवश्यक आहे</item>
-      <item quantity="one">किमान एक लोअरकेस अक्षर असणे आवश्यक आहे</item>
     </plurals>
     <plurals name="lockpassword_password_requires_uppercase" formatted="false" msgid="7999264563026517898">
+      <item quantity="one">किमान <xliff:g id="COUNT">%d</xliff:g> अप्परकेस अक्षर असणे आवश्यक आहे</item>
       <item quantity="other">किमान <xliff:g id="COUNT">%d</xliff:g> अप्परकेस अक्षरे असणे आवश्यक आहे</item>
-      <item quantity="one">किमान 1 अप्परकेस अक्षर असणे आवश्यक आहे</item>
     </plurals>
     <plurals name="lockpassword_password_requires_numeric" formatted="false" msgid="7935079851855168646">
+      <item quantity="one">किमान <xliff:g id="COUNT">%d</xliff:g> संख्यात्मक अंक असणे आवश्यक आहे</item>
       <item quantity="other">किमान <xliff:g id="COUNT">%d</xliff:g> संख्यात्मक अंक असणे आवश्यक आहे</item>
-      <item quantity="one">किमान एक संख्यात्मक अंक असणे आवश्यक आहे</item>
     </plurals>
     <plurals name="lockpassword_password_requires_symbols" formatted="false" msgid="3994046435150094132">
+      <item quantity="one">किमान <xliff:g id="COUNT">%d</xliff:g> विशेष चिन्ह असणे आवश्यक आहे</item>
       <item quantity="other">किमान <xliff:g id="COUNT">%d</xliff:g> विशेष चिन्हे असणे आवश्यक आहे</item>
-      <item quantity="one">किमान एक विशेष चिन्ह असणे आवश्यक आहे</item>
     </plurals>
     <plurals name="lockpassword_password_requires_nonletter" formatted="false" msgid="6878486326748506524">
+      <item quantity="one">किमान <xliff:g id="COUNT">%d</xliff:g> अक्षर नसलेला वर्ण असणे आवश्यक आहे</item>
       <item quantity="other">किमान <xliff:g id="COUNT">%d</xliff:g> अक्षर नसलेले वर्ण असणे आवश्यक आहे</item>
-      <item quantity="one">किमान एक अक्षर नसलेला वर्ण असणे आवश्यक आहे</item>
     </plurals>
     <string name="lockpassword_password_recently_used" msgid="8255729487108602924">"डिव्हाइस प्रशासक अलीकडील पासवर्ड वापरण्याची अनुमती देत नाही"</string>
     <string name="error_saving_password" msgid="8334882262622500658">"पासवर्ड सेव्ह करताना एरर आली"</string>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"डेमो मोडमधून बाहेर पडा"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"सेटअप समाप्त करा"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"आता नाही"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ड्राइव्ह करताना वैशिष्ट्य उपलब्ध नाही."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ड्राइव्ह करत असताना वापरकर्त्याला जोडू शकत नाही."</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index e89e2b6..a0701c8 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Permintaan penggandingan"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Sentuh untuk berganding dengan <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Bahasa"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Bunyi"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Kelantangan deringan"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Kelantangan navigasi"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Padamkan pengguna"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Pengguna baharu"</string>
     <string name="user_guest" msgid="3465399481257448601">"Tetamu"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Pentadbir"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Dilog masuk sebagai pentadbir"</string>
     <string name="user_switch" msgid="6544839750534690781">"Tukar"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Anda (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nama"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Cuba semula"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Langkau"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Tetapkan kunci skrin"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Pilih PIN anda"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Pilih corak anda"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Pilih kata laluan anda"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Kunci skrin semasa"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Untuk keselamatan, tetapkan corak"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Kosongkan"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Kosongkan"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Batal"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Sahkan"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Mestilah sekurang-kurangnya 4 aksara"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Kata laluan mestilah antara 4-8 aksara dengan sekurang-kurangnya 1 nombor"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Mestilah sekurang-kurangnya <xliff:g id="COUNT">%d</xliff:g> aksara"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN mestilah sekurang-kurangnya <xliff:g id="COUNT">%d</xliff:g> digit"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Mesti kurang daripada <xliff:g id="NUMBER">%d</xliff:g> aksara"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Pentadbir peranti tidak membenarkan penggunaan PIN terbaharu"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"PIN lazim disekat oleh pentadbir IT anda. Cuba PIN yang lain."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Tidak boleh menyertakan aksara yang tidak sah."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Kata laluan tidak sah, mesti sekurang-kurangnya 4 aksara."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Kata laluan tidak sah, mestilah 4-8 aksara, mengandungi sekurang-kurangnya 1 digit, 1 huruf, tiada ruang kosong."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Mesti mengandungi sekurang-kurangnya <xliff:g id="COUNT">%d</xliff:g> huruf</item>
       <item quantity="one">Mesti mengandungi sekurang-kurangnya 1 huruf</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"selesaikan persediaan"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"bukan sekarang"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Ciri tidak tersedia semasa anda memandu."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Tidak dapat menambah pengguna semasa memandu."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index f647bbf..afcb11b 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -74,24 +74,23 @@
     <string name="bluetooth_preference_no_found_devices" msgid="1391812056491062262">"မည်သည့် စက်ပစ္စည်းမျှ မရှိပါ"</string>
     <string name="bluetooth_preference_paired_dialog_title" msgid="2470829827455850904">"တွဲချိတ်ထားသည့် စက်ပစ္စည်း"</string>
     <string name="bluetooth_preference_paired_dialog_name_label" msgid="3528740139365123415">"အမည်"</string>
-    <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"အောက်ပါအတွက် သုံးရန်"</string>
+    <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"အတွက် သုံးရန်"</string>
     <string name="wifi_ssid_hint" msgid="4155050863239489553">"\'ဘလူးတုသ်\' စက် အမည်ကို ပြောင်းပါ"</string>
     <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"ဘလူးတုသ်တွဲချိတ်ရန် တောင်းဆိုချက်"</string>
     <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"တွဲချိတ်ရန်နှင့် ချိတ်ဆက်ရန်"</string>
     <string name="bluetooth" msgid="5235115159234688629">"ဘလူးတုသ်"</string>
     <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"ဘလူးတုသ် တွဲချိတ်ခြင်းကုဒ်"</string>
-    <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"ပင်နံပါတ်တွင် စာလုံး သို့မဟုတ် သင်္ကေတများပါဝင်သည်"</string>
-    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"တွဲချိတ်ရန်ကုဒ်ကို ထည့်ပြီး Return သို့မဟုတ် Enter ကို နှိပ်ပါ"</string>
-    <string name="bluetooth_pairing_request" msgid="4769675459526556801">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> နှင့် တွဲချိတ်လိုပါသလား။"</string>
+    <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"ပင်နံပါတ်တွင် စကားလုံးနှင့် သင်္ကေတများပါဝင်သည်"</string>
+    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"တွဲချိတ်ရန်ကုဒ်ကို ထည့်ပြီး \'Return\' သို့မဟုတ် \'Enter\' ကို နှိပ်ပါ"</string>
+    <string name="bluetooth_pairing_request" msgid="4769675459526556801">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>နှင့် တွဲချိတ်လိုပါသလား။"</string>
     <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"သင်၏ အဆက်အသွယ်များနှင့် ခေါ်ဆိုမှုမှတ်တမ်းကို <xliff:g id="DEVICE_NAME">%1$s</xliff:g> အား အသုံးပြုခွင့်ပေးပါ"</string>
     <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"ဤပင်နံပါတ်ကို အခြား စက်ပစ္စည်းတွင်လည်း ထည့်သွင်းရန် လိုအပ်နိုင်သည်။"</string>
     <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"ဤလျှို့ဝှက်ကုဒ်ကို အခြား စက်ပစ္စည်းတွင်လည်း ထည့်သွင်းရန် လိုအပ်နိုင်သည်။"</string>
     <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"ဂဏန်း ၁၆ လုံး ရှိရမည်"</string>
     <string name="bluetooth_pin_values_hint" msgid="1561325817559141687">"ပုံမှန် ၀၀၀၀ (သို့) ၁၂၃၄"</string>
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"တွဲချိတ်ရန် တောင်းဆိုချက်"</string>
-    <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> နှင့် တွဲချိတ်ရန် တို့ပါ။"</string>
+    <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>နှင့် တွဲချိတ်ရန် တို့ပါ။"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ဘာသာစကားများ"</string>
     <string name="sound_settings" msgid="3072423952331872246">"အသံ"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"ဖုန်းမြည်သံ အတိုးအကျယ်"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"လမ်းညွှန်မှု အသံ"</string>
@@ -123,7 +122,7 @@
     <string name="model_info" msgid="4966408071657934452">"မော်ဒယ်"</string>
     <string name="baseband_version" msgid="2370088062235041897">"baseband ဗားရှင်း"</string>
     <string name="kernel_version" msgid="7327212934187011508">"Kernel ဗားရှင်း"</string>
-    <string name="build_number" msgid="3997326631001009102">"တည်ဆောက်မှုနံပါတ်"</string>
+    <string name="build_number" msgid="3997326631001009102">"တည်ဆောင်မှုနံပါတ်"</string>
     <string name="device_info_not_available" msgid="2095601973977376655">"မရနိုင်ပါ"</string>
     <string name="device_status_activity_title" msgid="4083567497305368200">"အခြေအနေ"</string>
     <string name="device_status" msgid="267298179806290920">"အခြေအနေ"</string>
@@ -131,7 +130,7 @@
     <string name="device_status_summary" product="default" msgid="9130360324418117815">"ဖုန်းနံပါတ်၊ လိုင်းဆွဲအား စသည်။"</string>
     <string name="about_settings" msgid="4329457966672592345">"အကြောင်း"</string>
     <string name="about_summary" msgid="5374623866267691206">"Android <xliff:g id="VERSION">%1$s</xliff:g>"</string>
-    <string name="about_settings_summary" msgid="7975072809083281401">"ဥပဒေဆိုင်ရာအချက်အလက်၊ အခြေအနေ၊ ဆော့ဝဲလ်ဗားရှင်းကို ကြည့်ရှုရန်"</string>
+    <string name="about_settings_summary" msgid="7975072809083281401">"ဥပဒေဆိုင်ရာအချက်အလက်၊ အခြေအနေ၊ ဆော့ဝဲလ်ဗားရှင်ကို ကြည့်ရှုရန်"</string>
     <string name="legal_information" msgid="1838443759229784762">"ဥပဒေဆိုင်ရာ အချက်အလက်များ"</string>
     <string name="contributors_title" msgid="7698463793409916113">"ပံ့ပိုးသူများ"</string>
     <string name="manual" msgid="4819839169843240804">"ကိုယ်တိုင်ထည့်သွင်းရန်"</string>
@@ -139,10 +138,10 @@
     <string name="safety_and_regulatory_info" msgid="1204127697132067734">"လုံခြုံရေးနှင့် ထိန်းသိမ်းမှုလမ်းညွှန်"</string>
     <string name="copyright_title" msgid="4220237202917417876">"မူပိုင်ခွင့်"</string>
     <string name="license_title" msgid="936705938435249965">"လိုင်စင်"</string>
-    <string name="terms_title" msgid="5201471373602628765">"ဝန်ဆောင်မှုစည်းမျဉ်းများ"</string>
+    <string name="terms_title" msgid="5201471373602628765">"စည်းမျဉ်းများနှင့် အခြေအနေများ"</string>
     <string name="webview_license_title" msgid="2531829466541104826">"စနစ် WebView လိုင်စင်"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"နောက်ခံပုံများ"</string>
-    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"ဂြိုဟ်တုဓာတ်ပုံ ဝန်ဆောင်မှုပေးသူများ−\n©2014 CNES / Astrium၊ DigitalGlobe၊ Bluesky"</string>
+    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"ဂြိုလ်တုဓာတ်ပုံ ဝန်ဆောင်မှုပေးသူများ−\n©2014 CNES / Astrium၊ DigitalGlobe၊ Bluesky"</string>
     <string name="settings_license_activity_title" msgid="8499293744313077709">"ပြင်ပကုမ္ပဏီလိုင်စင်များ"</string>
     <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"လိုင်စင်များကို ဖွင့်ရာတွင် ပြသနာရှိနေသည်။"</string>
     <string name="settings_license_activity_loading" msgid="6163263123009681841">"ဖွင့်နေသည်…"</string>
@@ -167,11 +166,9 @@
     <string name="time_picker_title" msgid="7436045944320504639">"အချိန်"</string>
     <string name="user_add_user_menu" msgid="5319151436895941496">"အသုံးပြုသူ ထည့်ရန်"</string>
     <string name="user_add_account_menu" msgid="6625351983590713721">"အကောင့်ထည့်ရန်"</string>
-    <string name="user_delete_user_description" msgid="2300280525351142435">"အသုံးပြုသူကို ဖျက်ရန်"</string>
+    <string name="user_delete_user_description" msgid="2300280525351142435">"သုံးစွဲသူကို ဖျက်ရန်"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"အသုံးပြုသူ အသစ်"</string>
     <string name="user_guest" msgid="3465399481257448601">"ဧည့်သည်"</string>
-    <string name="user_admin" msgid="1535484812908584809">"စီမံခန့်ခွဲသူ"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"စီမံခန့်ခွဲသူအဖြစ် ဝင်ထားသည်"</string>
     <string name="user_switch" msgid="6544839750534690781">"ပြောင်းရန်"</string>
     <string name="current_user_name" msgid="3813671533249316823">"သင် (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"အမည်"</string>
@@ -205,7 +202,7 @@
     <string name="security_lock_pattern" msgid="1174352995619563104">"ပုံစံ"</string>
     <string name="security_lock_pin" msgid="4891899974369503200">"ပင်နံပါတ်"</string>
     <string name="security_lock_password" msgid="4420203740048322494">"စကားဝှက်"</string>
-    <string name="lock_settings_picker_title" msgid="6590330165050361632">"လော့ခ်ဖွင့်နည်း ရွေးပါ"</string>
+    <string name="lock_settings_picker_title" msgid="6590330165050361632">"လော့ခ်အမျိုးအစား ရွေးပါ"</string>
     <string name="screen_lock_options" msgid="7023338635352915768">"မျက်နှာပြင်လော့ခ်ချရန် ရွေးချယ်စရာများ"</string>
     <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"သင့်လော့ခ်ဖွင့်ပုံစံ ထည့်ခြင်း"</string>
     <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"အတည်ပြုရန်"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ထပ်လုပ်ရန်"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"ကျော်ရန်"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"မျက်နှာပြင်လော့ခ်တစ်ခု သတ်မှတ်ခြင်း"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"သင့် PIN နံပါတ် ရွေးချယ်ခြင်း"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"သင့်လော့ခ်ဖွင့်ပုံစံ ရွေးပါ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"သင့်စကားဝှက် ရွေးချယ်ခြင်း"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"လက်ရှိ မျက်နှာပြင်လော့ခ်"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"လုံခြုံရေးအတွက် ပုံစံတစ်ခု သတ်မှတ်ပါ"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"ရှင်းရန်"</string>
@@ -228,7 +223,7 @@
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"ပုံစံကိုထပ်ဆွဲပြီး အတည်ပြုရန်"</string>
     <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"အနည်းဆုံးအမှတ် ၄ မှတ်ချိတ်ပါ။ ထပ်လုပ်ပါ။"</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"ပုံစံ မှားနေသည်"</string>
-    <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"လော့ခ်ဖွင့်ရန်ပုံစံ ဆွဲနည်း"</string>
+    <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"လော့ခ်ဖွင့်ရန်ပုံစံ ဆွဲရန်နည်းလမ်း"</string>
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"ပုံစံကို သိမ်းရာတွင် အမှားရှိနေသည်"</string>
     <string name="okay" msgid="4589873324439764349">"OK"</string>
     <string name="remove_screen_lock_title" msgid="1234382338764193387">"မျက်နှာပြင်လော့ခ် ဖယ်လိုသလား။"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"ရှင်းရန်"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"မလုပ်တော့"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"အတည်ပြုရန်"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"အနည်းဆုံး အက္ခရာ ၄ လုံး ရှိရမည်"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"စကားဝှက်၌ စာ ၄−၈လုံး၊ ဂဏန်း ၁လုံး ပါရမည်"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"အနည်းဆုံး အက္ခရာ <xliff:g id="COUNT">%d</xliff:g> လုံး ရှိရမည်"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"ပင်နံပါတ်သည် အနည်းဆုံးဂဏန်း <xliff:g id="COUNT">%d</xliff:g> လုံးရှိရမည်"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"အက္ခရာ <xliff:g id="NUMBER">%d</xliff:g> လုံးထက် နည်းရပါမည်"</string>
@@ -258,10 +253,10 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"စက်ပစ္စည်း၏ စီမံခန့်ခွဲသူသည် မကြာသေးမီက အသုံးပြုခဲ့သည့် ပင်နံပါတ်ကို သုံးခွင့်မပြုပါ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"အသုံးပြုလေ့ရှိသော ပင်နံပါတ်များကို သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်။ အခြား ပင်နံပါတ်တစ်ခုဖြင့် စမ်းကြည့်ပါ။"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"၎င်းတွင် မမှန်ကန်သည့် အက္ခရာများ ပါဝင်၍မရပါ။"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"စကားဝှက် မမှန်ပါ၊ အနည်းဆုံး အက္ခရာ ၄ လုံး ရှိရမည်။"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"စကားဝှက် မမှန်ကန်ပါ၊ အက္ခရာ ၄−၈ လုံး၊ အနည်းဆုံး ဂဏန်း ၁ လုံး၊ စာလုံး ၁ လုံးရှိရမည်ဖြစ်ပြီး နေရာမခြားရပါ။"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
-      <item quantity="other">အနည်းဆုံး စာလုံး <xliff:g id="COUNT">%d</xliff:g> လုံးပါဝင်ရမည်</item>
-      <item quantity="one">အနည်းဆုံး စာလုံး ၁ လုံးပါဝင်ရမည်</item>
+      <item quantity="other">အနည်းဆုံး စကားလုံး <xliff:g id="COUNT">%d</xliff:g> လုံးပါဝင်ရမည်</item>
+      <item quantity="one">အနည်းဆုံး စကားလုံး ၁ လုံးပါဝင်ရမည်</item>
     </plurals>
     <plurals name="lockpassword_password_requires_lowercase" formatted="false" msgid="2267487180744744833">
       <item quantity="other">အနည်းဆုံး စာလုံးငယ် <xliff:g id="COUNT">%d</xliff:g> လုံးပါဝင်ရမည်</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"စနစ်ထည့်သွင်းမှု အပြီးသတ်ရန်"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ယခုမလုပ်ပါ"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"ကားမောင်းနေစဉ် ဝန်ဆောင်မှု မရနိုင်ပါ။"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ကားမောင်းနေစဉ် အသုံးပြုသူကို ထည့်၍မရပါ။"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index a1c48f6..08b307d 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Tilkoblingsforespørsel"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Trykk for å koble til <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Språk"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Lyd"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Ringevolum"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigasjonsvolum"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Slett bruker"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Ny bruker"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gjest"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Logget på som administrator"</string>
     <string name="user_switch" msgid="6544839750534690781">"Bytt"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Deg (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Navn"</string>
@@ -196,7 +193,7 @@
     <string name="remove_user_error_message" msgid="6803947507134323358">"Vil du prøve på nytt?"</string>
     <string name="remove_user_error_dismiss" msgid="4006591159426844335">"Avvis"</string>
     <string name="remove_user_error_retry" msgid="8291692909396995093">"Prøv på nytt"</string>
-    <string name="user_add_user_title" msgid="7458813670614932479">"Vil du legge til denne brukeren?"</string>
+    <string name="user_add_user_title" msgid="7458813670614932479">"Legg til en ny bruker?"</string>
     <string name="user_add_user_message_setup" msgid="6030901156040053106">"Når du legger til en ny bruker, må vedkommende konfigurere sitt eget område."</string>
     <string name="user_add_user_message_update" msgid="1528170913388932459">"Alle brukere kan oppdatere apper for alle andre brukere."</string>
     <string name="security_settings_title" msgid="6955331714774709746">"Sikkerhet"</string>
@@ -214,11 +211,9 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Prøv på nytt"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Hopp over"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Angi en skjermlås"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Velg PIN-koden din"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Velg mønster"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Velg passordet ditt"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Gjeldende skjermlås"</string>
-    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Angi et sikkerhetsmønster"</string>
+    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Angi et mønster for sikkerheten"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Fjern"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"Avbryt"</string>
     <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"Det nye opplåsingsmønsteret ditt"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Fjern"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Avbryt"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Bekreft"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Må bestå av minst fire tegn"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Passordet må være fire til åtte tegn med minst ett siffer"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Må være minst <xliff:g id="COUNT">%d</xliff:g> tegn"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN-koden må være på minst <xliff:g id="COUNT">%d</xliff:g> sifre"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Må inneholde færre enn <xliff:g id="NUMBER">%d</xliff:g> tegn"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Enhetsadministratoren forbyr nylig brukte PIN-koder"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Vanlige PIN-koder er blokkert av IT-administratoren din. Prøv en annen PIN-kode."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Dette kan ikke inkludere et ugyldig tegn."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Passordet er ugyldig – det må bestå av minst fire tegn."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Passordet er ugyldig. Det må være på fire til åtte tegn, inneholde minst ett siffer, én bokstav og ingen mellomrom."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Må inneholde minst <xliff:g id="COUNT">%d</xliff:g> bokstaver</item>
       <item quantity="one">Må inneholde minst én bokstav</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"fullfør konfigureringen"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ikke nå"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funksjonen er ikke tilgjengelig når du kjører."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Du kan ikke legge til en bruker mens du kjører."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 5f0f0b9..851c6ee 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -81,7 +81,7 @@
     <string name="bluetooth" msgid="5235115159234688629">"ब्लुटुथ"</string>
     <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"ब्लुटुथसँग जोडा बनाउने कोड"</string>
     <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"PIN मा अक्षर वा प्रतीकहरू समाविष्ट हुन्छन्"</string>
-    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"जोडा मिलाउने कोड टाइप गर्नुहोस् त्यसपछि फिर्ता गर्नुहोस् वा प्रविष्टि गर्नुहोस् नामक बटन थिच्नुहोस्"</string>
+    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"जोडा मिलाउने कोड टाइप गर्नुहोस् त्यसपछि फिर्ता गर्नुहोस् वा प्रविष्ट गर्नुहोस् नामक बटन थिच्नुहोस्"</string>
     <string name="bluetooth_pairing_request" msgid="4769675459526556801">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> सँग जोडा बनाउने हो?"</string>
     <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>लाई तपाईंका सम्पर्क ठेगानाहरू र फोन सम्पर्कको इतिहासमाथि पहुँच गर्न अनुमति दिनुहोस्"</string>
     <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"तपाईंले अर्को यन्त्रमा पनि यो PIN टाइप गर्नु पर्ने हुन सक्छ।"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"जोडा बनाउनका लागि गरिएको अनुरोध"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>सँग जोडा बनाउन ट्याप गर्नुहोस्।"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"भाषाहरू"</string>
     <string name="sound_settings" msgid="3072423952331872246">"आवाज"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"घन्टीको भोल्युम"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"नेभिगेसन भोल्युम"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"प्रयोगकर्ता मेटाउनुहोस्"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"नयाँ प्रयोगकर्ता"</string>
     <string name="user_guest" msgid="3465399481257448601">"अतिथि"</string>
-    <string name="user_admin" msgid="1535484812908584809">"प्रशासक"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"प्रशासकका रूपमा साइन इन गरियो"</string>
     <string name="user_switch" msgid="6544839750534690781">"बदल्नुहोस्‌"</string>
     <string name="current_user_name" msgid="3813671533249316823">"तपाईं (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"नाम"</string>
@@ -207,16 +204,14 @@
     <string name="security_lock_password" msgid="4420203740048322494">"पासवर्ड"</string>
     <string name="lock_settings_picker_title" msgid="6590330165050361632">"लकको प्रकार चयन गर्ने"</string>
     <string name="screen_lock_options" msgid="7023338635352915768">"स्क्रिन लकका विकल्पहरू"</string>
-    <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"आफ्नो ढाँचा प्रविष्टि गर्नुहोस्"</string>
+    <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"आफ्नो ढाँचा प्रविष्ट गर्नुहोस्"</string>
     <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"पुष्टि गर्नुहोस्"</string>
     <string name="lockpattern_restart_button_text" msgid="9355771277617537">"पुनः चित्रण गर्नुहोस्"</string>
     <string name="continue_button_text" msgid="5129979170426836641">"जारी राख्नुहोस्"</string>
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"पुनः प्रयास गर्ने"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"छाड्नुहोस्"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"स्क्रिन लक सेट गर्नुहोस्"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"आफ्नो PIN छनौट गर्नुहोस्"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"आफ्नो ढाँचा छनौट गर्नुहोस्"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"आफ्नो पासवर्ड छनौट गर्नुहोस्"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"हालको स्क्रिन लक"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"सुरक्षाका लागि ढाँचा सेट गर्नुहोस्"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"खाली गर्नुहोस्"</string>
@@ -233,10 +228,10 @@
     <string name="okay" msgid="4589873324439764349">"ठिक छ"</string>
     <string name="remove_screen_lock_title" msgid="1234382338764193387">"स्क्रिन लक हटाउने हो?"</string>
     <string name="remove_screen_lock_message" msgid="6675850371585564965">"यस कार्यले जुनसुकै व्यक्तिलाई तपाईंको खातामाथि पहुँच राख्न दिने छ"</string>
-    <string name="lock_settings_enter_pin" msgid="1669172111244633904">"आफ्नो PIN प्रविष्टि गर्नुहोस्"</string>
-    <string name="lock_settings_enter_password" msgid="2636669926649496367">"आफ्नो पासवर्ड प्रविष्टि गर्नुहोस्"</string>
+    <string name="lock_settings_enter_pin" msgid="1669172111244633904">"आफ्नो PIN प्रविष्ट गर्नुहोस्"</string>
+    <string name="lock_settings_enter_password" msgid="2636669926649496367">"आफ्नो पासवर्ड प्रविष्ट गर्नुहोस्"</string>
     <string name="choose_lock_pin_message" msgid="2963792070267774417">"सुरक्षाका ला‍गि एउटा PIN सेट गर्नुहोस्"</string>
-    <string name="confirm_your_pin_header" msgid="9096581288537156102">"आफ्नो PIN पुन: प्रविष्टि गर्नुहोस्"</string>
+    <string name="confirm_your_pin_header" msgid="9096581288537156102">"आफ्नो PIN पुन: प्रविष्ट गर्नुहोस्"</string>
     <string name="choose_lock_pin_hints" msgid="7362906249992020844">"PIN कम्तीमा ४ अङ्कको हुनै पर्ने"</string>
     <string name="lockpin_invalid_pin" msgid="2149191577096327424">"Pin अमान्य छ, यसमा अनिवार्य रूपमा कम्तीमा ४ अङ्क हुनु पर्छ।"</string>
     <string name="confirm_pins_dont_match" msgid="4607110139373520720">"PIN हरू मेल खाँदैनन्"</string>
@@ -244,12 +239,12 @@
     <string name="lockscreen_wrong_pin" msgid="4922465731473805306">"गलत PIN"</string>
     <string name="lockscreen_wrong_password" msgid="5757087577162231825">"गलत पासवर्ड"</string>
     <string name="choose_lock_password_message" msgid="6124341145027370784">"सुरक्षाका लागि पासवर्ड सेट गर्नुहोस्"</string>
-    <string name="confirm_your_password_header" msgid="7052891840366724938">"आफ्नो पासवर्ड पुन: प्रविष्टि गर्नुहोस्"</string>
+    <string name="confirm_your_password_header" msgid="7052891840366724938">"आफ्नो पासवर्ड पुन: प्रविष्ट गर्नुहोस्"</string>
     <string name="confirm_passwords_dont_match" msgid="7300229965206501753">"पासवर्डहरू मेल खाँदैनन्"</string>
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"खाली गर्नुहोस्"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"रद्द गर्नुहोस्"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"पुष्टि गर्नुहोस्"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"अनिवार्य रूपमा कम्तीमा ४ वर्णको हुनु पर्छ"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"पासवर्डमा अनिवार्य रूपमा कम्तीमा एउटा अङ्कसहित ४ देखि ८ वर्ण हुनु पर्छ"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"कम्तीमा <xliff:g id="COUNT">%d</xliff:g> वटा वर्ण हुनै पर्छ"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN कम्तीमा <xliff:g id="COUNT">%d</xliff:g> अङ्कको हुनै पर्छ"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"अनिवार्य रूपमा <xliff:g id="NUMBER">%d</xliff:g> भन्दा कम वर्ण हुनु पर्छ"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"यन्त्रको प्रशासकले पछिल्लो PIN प्रयोग गर्न अनुमति दिँदैन"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"तपाईंका IT व्यवस्थापकले धेरै प्रयोग हुने PIN हरूमाथि रोक लगाउनु भएको छ। कुनै फरक PIN प्रयोग गरी हेर्नुहोस्।"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"यसमा कुनै अमान्य वर्ण समावेश गर्न मिल्दैन।"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"अमान्य पासवर्ड, अनिवार्य रूपमा कम्तीमा ४ वर्णको हुनु पर्छ।"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"पासवर्ड अमान्य छ, अनिवार्य रूपमा ४ देखि ८ वर्ण हुनु पर्ने, कम्तीमा १ अङ्क, १ अक्षर हुनु पर्ने, ह्वाइटस्पेस हुन नहुने।"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">कम्तीमा <xliff:g id="COUNT">%d</xliff:g> वटा अक्षरहरू हुनु अनिवार्य छ</item>
       <item quantity="one">कम्तीमा एउटा अक्षर हुनु अनिवार्य छ</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"डेमो मोडबाट बाहिरिनुहोस्"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"सेटअप सम्पन्न गर्नुहोस्"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"अहिले होइन"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"सवारी साधन चलाइरहेको बेला यो सुविधा उपलब्ध छैन।"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"सवारी साधन चलाइरहेको बेला प्रयोगकर्तालाई थप्न सकिँदैन।"</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
deleted file mode 100644
index be2c363..0000000
--- a/res/values-night/colors.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 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.
--->
-<resources>
-    <color name="toggle_bg_disabled">@color/car_grey_800</color>
-    <color name="toggle_icon_disabled">@color/car_grey_400</color>
-    <color name="google_blue_600">@color/google_blue_600_light</color>
-
-    <color name="car_user_switcher_add_user_background_color">@color/car_dark_blue_grey_600</color>
-    <color name="car_user_switcher_current_user_color">@color/car_teal_200</color>
-</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 620fa97..615f8bc 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -83,7 +83,7 @@
     <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"Pincode bevat letters of symbolen"</string>
     <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"Typ de koppelingscode en druk op Return of Enter"</string>
     <string name="bluetooth_pairing_request" msgid="4769675459526556801">"Koppelen met <xliff:g id="DEVICE_NAME">%1$s</xliff:g>?"</string>
-    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"Toestaan dat <xliff:g id="DEVICE_NAME">%1$s</xliff:g> toegang heeft tot je contacten en gespreksgeschiedenis"</string>
+    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"Toestaan dat <xliff:g id="DEVICE_NAME">%1$s</xliff:g> toegang heeft tot je contacten en oproepgeschiedenis"</string>
     <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"U moet deze pincode wellicht ook opgeven op het andere apparaat."</string>
     <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"U moet deze toegangscode wellicht ook opgeven op het andere apparaat."</string>
     <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"Moet 16 cijfers zijn"</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Koppelingsverzoek"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tik om te koppelen met <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Talen"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Geluid"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Beltoonvolume"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigatievolume"</string>
@@ -103,10 +102,10 @@
     <string name="applications_settings" msgid="794261395191035632">"App-info"</string>
     <string name="disable_text" msgid="4358165448648990820">"Uitschakelen"</string>
     <string name="enable_text" msgid="1794971777861881238">"Inschakelen"</string>
-    <string name="permissions_label" msgid="2701446753515612685">"Rechten"</string>
+    <string name="permissions_label" msgid="2701446753515612685">"Machtigingen"</string>
     <string name="application_version_label" msgid="8556889839783311649">"Versie: %1$s"</string>
-    <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Geen rechten verleend"</string>
-    <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Geen rechten aangevraagd"</string>
+    <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Geen machtigingen verleend"</string>
+    <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Geen machtigingen aangevraagd"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Datagebruik"</string>
     <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Gegevensgebruik van app"</string>
     <string name="force_stop" msgid="2153183697014720520">"Gedwongen stoppen"</string>
@@ -140,7 +139,7 @@
     <string name="copyright_title" msgid="4220237202917417876">"Copyright"</string>
     <string name="license_title" msgid="936705938435249965">"Licentie"</string>
     <string name="terms_title" msgid="5201471373602628765">"Algemene voorwaarden"</string>
-    <string name="webview_license_title" msgid="2531829466541104826">"Systeemlicentie voor WebView"</string>
+    <string name="webview_license_title" msgid="2531829466541104826">"Systeemlicentie voor webweergave"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"Achtergronden"</string>
     <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Providers van satellietbeelden:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
     <string name="settings_license_activity_title" msgid="8499293744313077709">"Licenties van derden"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Gebruiker verwijderen"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nieuwe gebruiker"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gast"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Beheerder"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Ingelogd als beheerder"</string>
     <string name="user_switch" msgid="6544839750534690781">"Wijzigen"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Jij (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Naam"</string>
@@ -182,9 +179,9 @@
     <string name="user_details_title" msgid="1104762783367701498">"Gebruiker"</string>
     <string name="no_accounts_added" msgid="5148163140691096055">"Geen accounts toegevoegd"</string>
     <string name="account_list_title" msgid="7631588514613843065">"Accounts voor <xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g>"</string>
-    <string name="account_details_title" msgid="7529571432258448573">"Accountgegevens"</string>
+    <string name="account_details_title" msgid="7529571432258448573">"Accountinformatie"</string>
     <string name="add_account_title" msgid="5988746086885210040">"Account toevoegen"</string>
-    <string name="add_an_account" msgid="1072285034300995091">"Account toevoegen"</string>
+    <string name="add_an_account" msgid="1072285034300995091">"Een account toevoegen"</string>
     <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"Beperkte profielen kunnen geen accounts toevoegen"</string>
     <string name="remove_account_title" msgid="8840386525787836381">"Account verwijderen"</string>
     <string name="really_remove_account_title" msgid="3555164432587924900">"Account verwijderen?"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Opnieuw proberen"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Overslaan"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Een schermvergrendeling instellen"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Je pincode kiezen"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Je patroon kiezen"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Je wachtwoord kiezen"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Huidige schermvergrendeling"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Stel wachtwoord in voor beveiliging"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Wissen"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Wissen"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Annuleren"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Bevestigen"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Moet ten minste vier tekens zijn"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Het wachtwoord moet vier tot acht tekens lang zijn en ten minste één cijfer bevatten"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Moet ten minste <xliff:g id="COUNT">%d</xliff:g> tekens zijn"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Pincode moet ten minste <xliff:g id="COUNT">%d</xliff:g> cijfers zijn"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Moet korter zijn dan <xliff:g id="NUMBER">%d</xliff:g> tekens"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Apparaatbeheer staat het gebruik van een recente pincode niet toe"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Veelvoorkomende pincodes worden geblokkeerd door je IT-beheerder. Probeer een andere pincode."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Mag geen ongeldig teken bevatten"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Wachtwoord ongeldig. Moet ten minste vier tekens zijn."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Wachtwoord ongeldig, moet vier tot acht tekens lang zijn, zonder spaties en met ten minste één cijfer en één letter."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Moet ten minste <xliff:g id="COUNT">%d</xliff:g> letters bevatten</item>
       <item quantity="one">Moet ten minste één letter bevatten</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"instellen voltooien"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"niet nu"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Functie niet beschikbaar tijdens het rijden."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Kan geen gebruikers toevoegen tijdens het rijden."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 1bb7e45..542f4ad 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -18,280 +18,456 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="settings_label" msgid="5147911978211079839">"ସେଟିଙ୍ଗ"</string>
+    <!-- no translation found for settings_label (5147911978211079839) -->
+    <skip />
     <string name="more_settings_label" msgid="3867559443480110616">"ଅଧିକ"</string>
-    <string name="display_settings" msgid="5325515247739279185">"ଡିସ୍‌ପ୍ଲେ"</string>
-    <string name="brightness" msgid="2919605130898772866">"ଉଜ୍ଜ୍ୱଳତା ସ୍ତର"</string>
-    <string name="auto_brightness_title" msgid="9124647862844666581">"ଅନୁକୂଳ ଉଜ୍ଜ୍ୱଳତା"</string>
-    <string name="auto_brightness_summary" msgid="4741887033140384352">"ଉପଲବ୍ଧ ଆଲୋକ ପାଇଁ ଉଜ୍ଜ୍ୱଳତା ଅନୁକୂଳ କରନ୍ତୁ"</string>
-    <string name="condition_night_display_title" msgid="3777509730126972675">"ରାତି ଆଲୋକ ଅନ୍ ହୋଇଛି"</string>
-    <string name="keywords_display" msgid="3978416985146943922">"ସ୍କ୍ରୀନ୍‌, ଟଚ୍‌ସ୍କ୍ରୀନ୍‌"</string>
-    <string name="keywords_display_brightness_level" msgid="3956411572536209195">"ଡିମ୍‌ ସ୍କ୍ରିନ୍‌, ଟଚ୍‌ସ୍କ୍ରିନ୍‌, ବ୍ୟାଟେରୀ"</string>
-    <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"ଡିମ୍‌ ସ୍କ୍ରିନ୍‌, ଟଚ୍‌ସ୍କ୍ରିନ୍‌, ବ୍ୟାଟେରୀ"</string>
-    <string name="keywords_display_night_display" msgid="2922294576679769957">"ଅଳ୍ପ ଆଲୋକ ଥିବା ସ୍କ୍ରୀନ୍‌, ରାତି, ଟିଣ୍ଟ"</string>
+    <!-- no translation found for display_settings (5325515247739279185) -->
+    <skip />
+    <!-- no translation found for brightness (2919605130898772866) -->
+    <skip />
+    <!-- no translation found for auto_brightness_title (9124647862844666581) -->
+    <skip />
+    <!-- no translation found for auto_brightness_summary (4741887033140384352) -->
+    <skip />
+    <!-- no translation found for condition_night_display_title (3777509730126972675) -->
+    <skip />
+    <!-- no translation found for keywords_display (3978416985146943922) -->
+    <skip />
+    <!-- no translation found for keywords_display_brightness_level (3956411572536209195) -->
+    <skip />
+    <!-- no translation found for keywords_display_auto_brightness (2700310050333468752) -->
+    <skip />
+    <!-- no translation found for keywords_display_night_display (2922294576679769957) -->
+    <skip />
     <string name="night_mode_tile_label" msgid="6603597795502131664">"ନାଇଟ୍ ମୋଡ୍"</string>
-    <string name="wifi_settings" msgid="7701477685273103841">"ୱାଇ-ଫାଇ"</string>
-    <string name="wifi_settings_summary" msgid="6095898149997291025">"ୱାୟର୍‌ଲେସ୍‌ ଆକ୍ସେସ୍‌ ପଏଣ୍ଟର ସେଟ୍‌ଅପ୍‍ ଓ ପରିଚାଳନା"</string>
-    <string name="wifi_starting" msgid="473253087503153167">"ୱାଇ-ଫାଇ ଅନ୍‌ କରୁଛି…"</string>
-    <string name="wifi_stopping" msgid="3534173972547890148">"ୱାଇ-ଫାଇ ବନ୍ଦ‌ ହେଉଛି…"</string>
-    <string name="wifi_failed_forget_message" msgid="121732682699377206">"ନେଟ୍‌ୱର୍କ ଭୁଲିଯିବାରେ ବିଫଳ ହେଲା"</string>
-    <string name="wifi_failed_connect_message" msgid="4447498225022147324">"ନେଟ୍‌ୱର୍କକୁ ସଂଯୋଗ କରିପାରିଲା ନାହିଁ।"</string>
-    <string name="wifi_setup_add_network" msgid="3660498520389954620">"ନେଟ୍‌ୱର୍କ ଯୋଡ଼ନ୍ତୁ"</string>
-    <string name="wifi_disabled" msgid="5013262438128749950">"ୱାଇ-ଫାଇ ଅକ୍ଷମ କରାଯାଇଛି"</string>
-    <string name="wifi_setup_connect" msgid="3512399573397979101">"ସଂଯୋଗ"</string>
-    <string name="wifi_password" msgid="5565632142720292397">"ପାସୱର୍ଡ"</string>
-    <string name="wifi_show_password" msgid="8423293211933521097">"ପାସ୍‍ୱର୍ଡ ଦେଖାନ୍ତୁ"</string>
-    <string name="wifi_ssid" msgid="488604828159458741">"ନେଟୱାର୍କ ନାମ"</string>
-    <string name="wifi_security" msgid="158358046038876532">"ସୁରକ୍ଷା"</string>
-    <string name="wifi_signal" msgid="1817579728350364549">"ସିଗନାଲ୍‌ ଦକ୍ଷତା"</string>
-    <string name="wifi_status" msgid="5688013206066543952">"ସ୍ଥିତି"</string>
-    <string name="wifi_speed" msgid="1650692446731850781">"ଲିଙ୍କର ବେଗ"</string>
-    <string name="wifi_frequency" msgid="8951455949682864922">"ବାରମ୍ଵାରତା"</string>
-    <string name="wifi_ip_address" msgid="3128140627890954061">"IP ଠିକଣା"</string>
-    <string name="access_point_tag_key" msgid="1517143378973053337">"access_point_tag_key"</string>
-  <string-array name="wifi_signals">
-    <item msgid="4897376984576812606">"ଖରାପ"</item>
-    <item msgid="2032262610626057081">"ଠିକଠାକ"</item>
-    <item msgid="3859756017461098953">"ଭଲ"</item>
-    <item msgid="1521103743353335724">"ସର୍ବୋତ୍ତମ"</item>
-  </string-array>
-    <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"ବ୍ଲୁଟୁଥ୍‌"</string>
-    <string name="bluetooth_quick_toggle_summary" msgid="4390699431893305353">"ବ୍ଲୁଟୁଥ୍‌ ଅନ୍‌ କରନ୍ତୁ"</string>
-    <string name="bluetooth_settings" msgid="3878243366013638982">"ବ୍ଲୁଟୁଥ୍‌"</string>
-    <string name="bluetooth_disabled" msgid="4187409401590350572">"ବ୍ଲୁ-ଟୁଥ୍‌ ଅକ୍ଷମ ହୋଇଛି"</string>
-    <string name="bluetooth_settings_title" msgid="3794688574569688649">"ବ୍ଲୁଟୁଥ୍‌"</string>
-    <string name="bluetooth_settings_summary" msgid="4023303473646769835">"ସଂଯୋଗଗୁଡ଼ିକର ପରିଚାଳନା, ଡିଭାଇସ୍‌ ନାମ ଓ ଖୋଜିବାଯୋଗ୍ୟତା ସେଟ୍‍ କରନ୍ତୁ"</string>
-    <string name="bluetooth_talkback_computer" msgid="5223330129934365312">"କମ୍ପ୍ୟୁଟର୍"</string>
-    <string name="bluetooth_talkback_headset" msgid="6155254514321149935">"ହେଡସେଟ୍‍"</string>
-    <string name="bluetooth_talkback_phone" msgid="8833977851215000426">"ଫୋନ୍"</string>
-    <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"ଇମେଜିଙ୍ଗ"</string>
-    <string name="bluetooth_talkback_headphone" msgid="5362155791551671490">"ହେଡ୍‌ଫୋନ୍‌"</string>
-    <string name="bluetooth_talkback_input_peripheral" msgid="868933277567862622">"ଇନ୍‌ପୁଟ୍‌ ଉପକରଣ"</string>
-    <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"ବ୍ଲୁଟୁଥ୍‌"</string>
-    <string name="bluetooth_preference_paired_devices" msgid="5875643105380630583">"ପେୟାର୍‌ ହୋଇଥିବା ଡିଭାଇସ୍‌ଗୁଡ଼ିକ"</string>
-    <string name="bluetooth_preference_found_devices" msgid="125155123214560511">"ଉପଲବ୍ଧ ଡିଭାଇସ୍"</string>
-    <string name="bluetooth_preference_no_paired_devices" msgid="483742146117390001">"ପେୟାର୍‌ ହୋଇଥିବା କୌଣସି ଡିଭାଇସ୍‌ ନାହିଁ"</string>
-    <string name="bluetooth_preference_no_found_devices" msgid="1391812056491062262">"ଡିଭାଇସ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
-    <string name="bluetooth_preference_paired_dialog_title" msgid="2470829827455850904">"ପେୟାର୍‌ ହୋଇଥିବା ଡିଭାଇସ୍‌"</string>
-    <string name="bluetooth_preference_paired_dialog_name_label" msgid="3528740139365123415">"ନାମ"</string>
-    <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"ଏହା ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="wifi_ssid_hint" msgid="4155050863239489553">"ବ୍ଲୁଟୁଥ୍‌ ଡିଭାଇସ୍‌ର ନାମ ବଦଳାନ୍ତୁ"</string>
-    <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"ବ୍ଲୁଟୂଥ୍‍ ପେୟାର୍ କରିବା ପାଇଁ ଅନୁରୋଧ"</string>
-    <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"ପେୟାର୍‌ ଓ ସଂଯୋଗ"</string>
-    <string name="bluetooth" msgid="5235115159234688629">"ବ୍ଲୁଟୁଥ୍‌"</string>
-    <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"ବ୍ଲୁଟୂଥ୍‌ ପେୟାରିଂ କୋଡ୍"</string>
-    <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"ପିନ୍‌ରେ ଅକ୍ଷର କିମ୍ୱା ସଙ୍କେତ ରହିଥାଏ"</string>
-    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"ପେୟାରିଂ କୋଡ୍ ଟାଇପ୍‌ କରନ୍ତୁ ତାପରେ ଫେରନ୍ତୁ କିମ୍ବା ପ୍ରବେଶ ଦବାନ୍ତୁ"</string>
-    <string name="bluetooth_pairing_request" msgid="4769675459526556801">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ସହ ପେୟାର୍‌ କରିବେ?"</string>
-    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"ଆପଣଙ୍କ ଯୋଗାଯୋଗ ଓ କଲ୍‌ ହିଷ୍ଟୋରୀକୁ ଆକ୍‌ସେସ୍‌ କରିବାକୁ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
-    <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"ଆପଣ ଏହି PIN ଅନ୍ୟ ଡିଭାଇସ୍‌ରେ ମଧ୍ୟ ଟାଇପ୍‌ କରିବା ଆବଶ୍ୟକ କରିପାରନ୍ତି।"</string>
-    <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"ଏହି ପାସ୍‌-କୀକୁ ଅନ୍ୟ ଡିଭାଇସ୍‌ରେ ମଧ୍ୟ ଟାଇପ୍‌ କରିବା ଆବଶ୍ୟକ ହୋଇପରେ।"</string>
-    <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"16 ଅଙ୍କ ବିଶିଷ୍ଟ ହେବା ଦରକାର"</string>
-    <string name="bluetooth_pin_values_hint" msgid="1561325817559141687">"ସାଧାରଣତଃ 0000 କିମ୍ବା 1234"</string>
-    <string name="bluetooth_notif_title" msgid="8374602799367803335">"ପେୟାରିଙ୍ଗ ଅନୁରୋଧ"</string>
-    <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ସହ ପେୟାର୍‌ କରିବାକୁ ଟାପ୍‌ କରନ୍ତୁ"</string>
+    <!-- no translation found for wifi_settings (7701477685273103841) -->
+    <skip />
+    <!-- no translation found for wifi_settings_summary (6095898149997291025) -->
+    <skip />
+    <!-- no translation found for wifi_starting (473253087503153167) -->
+    <skip />
+    <!-- no translation found for wifi_stopping (3534173972547890148) -->
+    <skip />
+    <!-- no translation found for wifi_failed_forget_message (121732682699377206) -->
+    <skip />
+    <!-- no translation found for wifi_failed_connect_message (4447498225022147324) -->
+    <skip />
+    <!-- no translation found for wifi_setup_add_network (3660498520389954620) -->
+    <skip />
+    <!-- no translation found for wifi_disabled (5013262438128749950) -->
+    <skip />
+    <!-- no translation found for wifi_setup_connect (3512399573397979101) -->
+    <skip />
+    <!-- no translation found for wifi_password (5565632142720292397) -->
+    <skip />
+    <!-- no translation found for wifi_show_password (8423293211933521097) -->
+    <skip />
+    <!-- no translation found for wifi_ssid (488604828159458741) -->
+    <skip />
+    <!-- no translation found for wifi_security (158358046038876532) -->
+    <skip />
+    <!-- no translation found for wifi_signal (1817579728350364549) -->
+    <skip />
+    <!-- no translation found for wifi_status (5688013206066543952) -->
+    <skip />
+    <!-- no translation found for wifi_speed (1650692446731850781) -->
+    <skip />
+    <!-- no translation found for wifi_frequency (8951455949682864922) -->
+    <skip />
+    <!-- no translation found for wifi_ip_address (3128140627890954061) -->
+    <skip />
+    <!-- no translation found for access_point_tag_key (1517143378973053337) -->
+    <skip />
+    <!-- no translation found for wifi_signals:0 (4897376984576812606) -->
+    <!-- no translation found for wifi_signals:1 (2032262610626057081) -->
+    <!-- no translation found for wifi_signals:2 (3859756017461098953) -->
+    <!-- no translation found for wifi_signals:3 (1521103743353335724) -->
+    <!-- no translation found for bluetooth_quick_toggle_title (637869245038061523) -->
+    <skip />
+    <!-- no translation found for bluetooth_quick_toggle_summary (4390699431893305353) -->
+    <skip />
+    <!-- no translation found for bluetooth_settings (3878243366013638982) -->
+    <skip />
+    <!-- no translation found for bluetooth_disabled (4187409401590350572) -->
+    <skip />
+    <!-- no translation found for bluetooth_settings_title (3794688574569688649) -->
+    <skip />
+    <!-- no translation found for bluetooth_settings_summary (4023303473646769835) -->
+    <skip />
+    <!-- no translation found for bluetooth_talkback_computer (5223330129934365312) -->
+    <skip />
+    <!-- no translation found for bluetooth_talkback_headset (6155254514321149935) -->
+    <skip />
+    <!-- no translation found for bluetooth_talkback_phone (8833977851215000426) -->
+    <skip />
+    <!-- no translation found for bluetooth_talkback_imaging (8762390801115154654) -->
+    <skip />
+    <!-- no translation found for bluetooth_talkback_headphone (5362155791551671490) -->
+    <skip />
+    <!-- no translation found for bluetooth_talkback_input_peripheral (868933277567862622) -->
+    <skip />
+    <!-- no translation found for bluetooth_talkback_bluetooth (1715933297419387985) -->
+    <skip />
+    <!-- no translation found for bluetooth_preference_paired_devices (5875643105380630583) -->
+    <skip />
+    <!-- no translation found for bluetooth_preference_found_devices (125155123214560511) -->
+    <skip />
+    <!-- no translation found for bluetooth_preference_no_paired_devices (483742146117390001) -->
+    <skip />
+    <!-- no translation found for bluetooth_preference_no_found_devices (1391812056491062262) -->
+    <skip />
+    <!-- no translation found for bluetooth_preference_paired_dialog_title (2470829827455850904) -->
+    <skip />
+    <!-- no translation found for bluetooth_preference_paired_dialog_name_label (3528740139365123415) -->
+    <skip />
+    <!-- no translation found for bluetooth_device_advanced_profile_header_title (8165093257483965783) -->
+    <skip />
+    <!-- no translation found for wifi_ssid_hint (4155050863239489553) -->
+    <skip />
+    <!-- no translation found for bluetooth_notif_ticker (7192577740198156792) -->
+    <skip />
+    <!-- no translation found for bluetooth_device_context_pair_connect (3138105800372470422) -->
+    <skip />
+    <!-- no translation found for bluetooth (5235115159234688629) -->
+    <skip />
+    <!-- no translation found for bluetooth_pairing_key_msg (5066825929751599037) -->
+    <skip />
+    <!-- no translation found for bluetooth_enable_alphanumeric_pin (1636575922217263060) -->
+    <skip />
+    <!-- no translation found for bluetooth_enter_passkey_msg (5955236916732265593) -->
+    <skip />
+    <!-- no translation found for bluetooth_pairing_request (4769675459526556801) -->
+    <skip />
+    <!-- no translation found for bluetooth_pairing_shares_phonebook (2015966932886300630) -->
+    <skip />
+    <!-- no translation found for bluetooth_enter_pin_other_device (7825091249522704764) -->
+    <skip />
+    <!-- no translation found for bluetooth_enter_passkey_other_device (7147248221018865922) -->
+    <skip />
+    <!-- no translation found for bluetooth_pin_values_hint_16_digits (418776900816984778) -->
+    <skip />
+    <!-- no translation found for bluetooth_pin_values_hint (1561325817559141687) -->
+    <skip />
+    <!-- no translation found for bluetooth_notif_title (8374602799367803335) -->
+    <skip />
+    <!-- no translation found for bluetooth_notif_message (1060821000510108726) -->
+    <skip />
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ଭାଷା"</string>
-    <string name="sound_settings" msgid="3072423952331872246">"ଶବ୍ଦ"</string>
-    <string name="ring_volume_title" msgid="3135241004980719442">"ରିଙ୍ଗ ଭଲ୍ୟୁମ୍"</string>
-    <string name="navi_volume_title" msgid="946292066759195165">"ନେଭିଗେସନ୍‌ ଭଲ୍ୟୁମ୍‌"</string>
-    <string name="incoming_call_volume_title" msgid="6972117872424656876">"ରିଙ୍ଗଟୋନ୍‌"</string>
-    <string name="notification_volume_title" msgid="6749411263197157876">"ବିଜ୍ଞପ୍ତି"</string>
-    <string name="media_volume_title" msgid="6697416686272606865">"ମିଡିଆ"</string>
-    <string name="media_volume_summary" msgid="2961762827637127239">"ମ୍ୟୁଜିକ୍‌ ଓ ଭିଡିଓଗୁଡିକ ପାଇଁ ଭଲ୍ୟୁମ ସେଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="alarm_volume_title" msgid="840384014895796587">"ଆଲାର୍ମ"</string>
-    <string name="applications_settings" msgid="794261395191035632">"ଆପ୍‌ ସୂଚନା"</string>
-    <string name="disable_text" msgid="4358165448648990820">"ଅକ୍ଷମ"</string>
-    <string name="enable_text" msgid="1794971777861881238">"ସକ୍ଷମ"</string>
-    <string name="permissions_label" msgid="2701446753515612685">"ଅନୁମତିଗୁଡ଼ିକ"</string>
-    <string name="application_version_label" msgid="8556889839783311649">"ଭର୍ସନ୍‌: %1$s"</string>
-    <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"କୌଣସି ଅନୁମତି ଦିଆଯାଇ ନାହିଁ"</string>
-    <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"କୌଣସି ଅନୁମତିର ଅନୁରୋଧ କରାଯାଇ ନାହିଁ"</string>
-    <string name="data_usage_summary_title" msgid="4368024763485916986">"ଡାଟାର ବ୍ୟବହାର"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"ଆପ୍‌ ଦ୍ୱାରା ଡାଟା ବ୍ୟବହାର"</string>
-    <string name="force_stop" msgid="2153183697014720520">"ବାଧ୍ୟତାର ସହ ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="computing_size" msgid="5791407621793083965">"ଗଣୁଛି…"</string>
-    <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
-      <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>ଟି ଅତିରିକ୍ତ ଅନୁମତି</item>
-      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>ଟି ଅତିରିକ୍ତ ଅନୁମତି</item>
-    </plurals>
-    <string name="system_setting_title" msgid="6864599341809463440">"ସିଷ୍ଟମ୍‌"</string>
-    <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"ସିଷ୍ଟମ୍‌ ଅପଡେଟ୍‌‍"</string>
+    <!-- no translation found for sound_settings (3072423952331872246) -->
+    <skip />
+    <!-- no translation found for ring_volume_title (3135241004980719442) -->
+    <skip />
+    <!-- no translation found for navi_volume_title (946292066759195165) -->
+    <skip />
+    <!-- no translation found for incoming_call_volume_title (6972117872424656876) -->
+    <skip />
+    <!-- no translation found for notification_volume_title (6749411263197157876) -->
+    <skip />
+    <!-- no translation found for media_volume_title (6697416686272606865) -->
+    <skip />
+    <!-- no translation found for media_volume_summary (2961762827637127239) -->
+    <skip />
+    <!-- no translation found for alarm_volume_title (840384014895796587) -->
+    <skip />
+    <!-- no translation found for applications_settings (794261395191035632) -->
+    <skip />
+    <!-- no translation found for disable_text (4358165448648990820) -->
+    <skip />
+    <!-- no translation found for enable_text (1794971777861881238) -->
+    <skip />
+    <!-- no translation found for permissions_label (2701446753515612685) -->
+    <skip />
+    <!-- no translation found for application_version_label (8556889839783311649) -->
+    <skip />
+    <!-- no translation found for runtime_permissions_summary_no_permissions_granted (6001439205270250021) -->
+    <skip />
+    <!-- no translation found for runtime_permissions_summary_no_permissions_requested (4074220596273432442) -->
+    <skip />
+    <!-- no translation found for data_usage_summary_title (4368024763485916986) -->
+    <skip />
+    <!-- no translation found for data_usage_app_summary_title (5012851696585421420) -->
+    <skip />
+    <!-- no translation found for force_stop (2153183697014720520) -->
+    <skip />
+    <!-- no translation found for computing_size (5791407621793083965) -->
+    <skip />
+    <!-- no translation found for runtime_permissions_additional_count (3513360187065317613) -->
+    <!-- no translation found for system_setting_title (6864599341809463440) -->
+    <skip />
+    <!-- no translation found for system_update_settings_list_item_title (5182439376921868735) -->
+    <skip />
     <string name="system_update_settings_list_item_summary" msgid="7395202602021608371"></string>
-    <string name="firmware_version" msgid="8491753744549309333">"Android ଭର୍ସନ୍‌"</string>
-    <string name="security_patch" msgid="4794276590178386903">"Android ସୁରକ୍ଷା ପାଚ୍‌ ସ୍ତର"</string>
-    <string name="model_info" msgid="4966408071657934452">"ମଡେଲ୍‌"</string>
-    <string name="baseband_version" msgid="2370088062235041897">"ବେସ୍‌ବ୍ୟାଣ୍ଡ ଭର୍ସନ୍‌"</string>
-    <string name="kernel_version" msgid="7327212934187011508">"କର୍ନେଲ୍‌ ଭର୍ସନ୍‌"</string>
-    <string name="build_number" msgid="3997326631001009102">"ନମ୍ୱ‍ର୍‌ ଗଠନ କରନ୍ତୁ"</string>
-    <string name="device_info_not_available" msgid="2095601973977376655">"ଉପଲବ୍ଧ ନାହିଁ"</string>
-    <string name="device_status_activity_title" msgid="4083567497305368200">"ସ୍ଥିତି"</string>
-    <string name="device_status" msgid="267298179806290920">"ସ୍ଥିତି"</string>
-    <string name="device_status_summary" product="tablet" msgid="600543254608862075">"ବ୍ୟାଟେରୀ, ନେଟ୍‌ୱର୍କର ସ୍ଥିତି ଓ ଅନ୍ୟାନ୍ୟ ସୂଚନା"</string>
-    <string name="device_status_summary" product="default" msgid="9130360324418117815">"ଫୋନ ନମ୍ୱର, ସିଗ୍ନାଲ ଆଦି"</string>
-    <string name="about_settings" msgid="4329457966672592345">"ପରିଚୟ"</string>
-    <string name="about_summary" msgid="5374623866267691206">"Android <xliff:g id="VERSION">%1$s</xliff:g>"</string>
-    <string name="about_settings_summary" msgid="7975072809083281401">"ଆଇନଗତ ସୂଚନା, ଷ୍ଟେଟସ୍, ସଫ୍ଟୱେର୍‌ ଭର୍ସନ୍‌ ଦେଖନ୍ତୁ"</string>
-    <string name="legal_information" msgid="1838443759229784762">"ଆଇନ ସୂଚନା"</string>
-    <string name="contributors_title" msgid="7698463793409916113">"ଅବଦାନକାରୀ"</string>
-    <string name="manual" msgid="4819839169843240804">"ମାନୁଆଲ୍‌"</string>
-    <string name="regulatory_labels" msgid="3165587388499646779">"ରେଗୁଲେଟରୀ ଲେବଲ୍ସ"</string>
-    <string name="safety_and_regulatory_info" msgid="1204127697132067734">"ସୁରକ୍ଷା ଓ ନିୟାମକ ପୁସ୍ତିକା"</string>
-    <string name="copyright_title" msgid="4220237202917417876">"କପୀରାଇଟ୍‌"</string>
-    <string name="license_title" msgid="936705938435249965">"ଲାଇସେନ୍ସ"</string>
-    <string name="terms_title" msgid="5201471373602628765">"ନିୟମ ଓ ସର୍ତ୍ତାବଳୀ"</string>
-    <string name="webview_license_title" msgid="2531829466541104826">"ସିଷ୍ଟମ୍‌ ୱେବ୍‌‌ଭ୍ୟୁ ଲାଇସେନ୍ସ"</string>
-    <string name="wallpaper_attributions" msgid="9201272150014500697">"ୱାଲପେପର୍‌"</string>
-    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"ସ୍ୟାଟେଲାଇଟ୍‌ ଇମେଜେରୀ ପ୍ରଦାତା:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
-    <string name="settings_license_activity_title" msgid="8499293744313077709">"ତୃତୀୟ-ପକ୍ଷ ଲାଇସେନ୍ସ"</string>
-    <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"ଲାଇସେନ୍ସ ଲୋଡ୍‌ କରିବାରେ ସମସ୍ୟା ହୋଇଛି।"</string>
-    <string name="settings_license_activity_loading" msgid="6163263123009681841">"ଲୋଡ୍ କରୁଛି…"</string>
-    <string name="date_and_time_settings_title" msgid="4058492663544475485">"ତାରିଖ ଓ ସମୟ"</string>
-    <string name="date_and_time_settings_title_setup_wizard" msgid="7580119979694174107">"ତାରିଖ ଓ ସମୟ ସେଟ କରନ୍ତୁ"</string>
-    <string name="date_and_time_settings_summary" msgid="7669856855390804666">"ତାରିଖ, ସମୟ, ସମୟ ଅଞ୍ଚଳ, ଓ ଫର୍ମାଟ୍‌ ସେଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="date_time_auto" msgid="3570339569471779767">"ଅଟୋମେଟିକ୍ ତାରିଖ ଓ ସମୟ"</string>
-    <string name="date_time_auto_summary" msgid="3311706425095342759">"ନେଟ୍‌ୱର୍କ ପ୍ରଦତ୍ତ ସମୟ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="zone_auto" msgid="3701878581920206160">"ସ୍ୱଚାଳିତ ସମୟ କ୍ଷେତ୍ର"</string>
-    <string name="zone_auto_summary" msgid="4345856882906981864">"ନେଟ୍‌ୱର୍କ ପ୍ରଦତ୍ତ ସମୟ କ୍ଷେତ୍ର ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="date_time_24hour_title" msgid="3025576547136168692">"24‑ଘଣ୍ଟିଆ ଫର୍ମାଟ୍‌"</string>
-    <string name="date_time_24hour" msgid="1137618702556486913">"24-ଘଣ୍ଟିଆ ଫର୍ମାଟ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="date_time_set_time_title" msgid="5884883050656937853">"ସମୟ"</string>
-    <string name="date_time_set_time" msgid="6449555153906058248">"ସମୟ ସେଟ୍ କରନ୍ତୁ"</string>
-    <string name="date_time_set_timezone_title" msgid="3001779256157093425">"ଟାଇମ୍‌ ଜୋନ୍‌"</string>
-    <string name="date_time_set_timezone" msgid="4759353576185916944">"ସମୟ କ୍ଷେତ୍ର ବାଛନ୍ତୁ"</string>
-    <string name="date_time_set_date_title" msgid="6834785820357051138">"ତାରିଖ"</string>
-    <string name="date_time_set_date" msgid="2537494485643283230">"ତାରିଖ ସେଟ୍‍ କରନ୍ତୁ"</string>
-    <string name="zone_list_menu_sort_alphabetically" msgid="7041628618528523514">"ବର୍ଣ୍ଣାନୁକ୍ରମିକ ସର୍ଟ୍ କରନ୍ତୁ"</string>
-    <string name="zone_list_menu_sort_by_timezone" msgid="4944880536057914136">"ସମୟ କ୍ଷେତ୍ର ଦ୍ୱାରା କ୍ରମବଦ୍ଧ"</string>
-    <string name="date_picker_title" msgid="1533614225273770178">"ତାରିଖ"</string>
-    <string name="time_picker_title" msgid="7436045944320504639">"ସମୟ"</string>
-    <string name="user_add_user_menu" msgid="5319151436895941496">"ୟୁଜର୍‍ଙ୍କୁ ଯୋଡ଼ନ୍ତୁ"</string>
-    <string name="user_add_account_menu" msgid="6625351983590713721">"ଆକାଉଣ୍ଟ ଯୋଡ଼ନ୍ତୁ"</string>
-    <string name="user_delete_user_description" msgid="2300280525351142435">"ୟୁଜର୍‌‌ଙ୍କୁ ଡିଲିଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="user_new_user_name" msgid="7115771396412339662">"ନୂଆ ୟୁଜର୍‌"</string>
-    <string name="user_guest" msgid="3465399481257448601">"ଅତିଥି"</string>
-    <string name="user_admin" msgid="1535484812908584809">"ଆଡ୍‌ମିନ୍‌"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"ଆଡ୍‍ମିନ୍‍ ଭାବରେ ସାଇନ୍‍ ଇନ୍‍ କରିଛନ୍ତି"</string>
-    <string name="user_switch" msgid="6544839750534690781">"ବଦଳାନ୍ତୁ"</string>
-    <string name="current_user_name" msgid="3813671533249316823">"ଆପଣ (%1$s)"</string>
-    <string name="user_name_label" msgid="3210832645046206845">"ନାମ"</string>
-    <string name="user_summary_not_set_up" msgid="1473688119241224145">"ସେଟ୍ ହୋଇନାହିଁ"</string>
+    <!-- no translation found for firmware_version (8491753744549309333) -->
+    <skip />
+    <!-- no translation found for security_patch (4794276590178386903) -->
+    <skip />
+    <!-- no translation found for model_info (4966408071657934452) -->
+    <skip />
+    <!-- no translation found for baseband_version (2370088062235041897) -->
+    <skip />
+    <!-- no translation found for kernel_version (7327212934187011508) -->
+    <skip />
+    <!-- no translation found for build_number (3997326631001009102) -->
+    <skip />
+    <!-- no translation found for device_info_not_available (2095601973977376655) -->
+    <skip />
+    <!-- no translation found for device_status_activity_title (4083567497305368200) -->
+    <skip />
+    <!-- no translation found for device_status (267298179806290920) -->
+    <skip />
+    <!-- no translation found for device_status_summary (600543254608862075) -->
+    <skip />
+    <!-- no translation found for device_status_summary (9130360324418117815) -->
+    <skip />
+    <!-- no translation found for about_settings (4329457966672592345) -->
+    <skip />
+    <!-- no translation found for about_summary (5374623866267691206) -->
+    <skip />
+    <!-- no translation found for about_settings_summary (7975072809083281401) -->
+    <skip />
+    <!-- no translation found for legal_information (1838443759229784762) -->
+    <skip />
+    <!-- no translation found for contributors_title (7698463793409916113) -->
+    <skip />
+    <!-- no translation found for manual (4819839169843240804) -->
+    <skip />
+    <!-- no translation found for regulatory_labels (3165587388499646779) -->
+    <skip />
+    <!-- no translation found for safety_and_regulatory_info (1204127697132067734) -->
+    <skip />
+    <!-- no translation found for copyright_title (4220237202917417876) -->
+    <skip />
+    <!-- no translation found for license_title (936705938435249965) -->
+    <skip />
+    <!-- no translation found for terms_title (5201471373602628765) -->
+    <skip />
+    <!-- no translation found for webview_license_title (2531829466541104826) -->
+    <skip />
+    <!-- no translation found for wallpaper_attributions (9201272150014500697) -->
+    <skip />
+    <!-- no translation found for wallpaper_attributions_values (4292446851583307603) -->
+    <skip />
+    <!-- no translation found for settings_license_activity_title (8499293744313077709) -->
+    <skip />
+    <!-- no translation found for settings_license_activity_unavailable (6104592821991010350) -->
+    <skip />
+    <!-- no translation found for settings_license_activity_loading (6163263123009681841) -->
+    <skip />
+    <!-- no translation found for date_and_time_settings_title (4058492663544475485) -->
+    <skip />
+    <!-- no translation found for date_and_time_settings_title_setup_wizard (7580119979694174107) -->
+    <skip />
+    <!-- no translation found for date_and_time_settings_summary (7669856855390804666) -->
+    <skip />
+    <!-- no translation found for date_time_auto (3570339569471779767) -->
+    <skip />
+    <!-- no translation found for date_time_auto_summary (3311706425095342759) -->
+    <skip />
+    <!-- no translation found for zone_auto (3701878581920206160) -->
+    <skip />
+    <!-- no translation found for zone_auto_summary (4345856882906981864) -->
+    <skip />
+    <!-- no translation found for date_time_24hour_title (3025576547136168692) -->
+    <skip />
+    <!-- no translation found for date_time_24hour (1137618702556486913) -->
+    <skip />
+    <!-- no translation found for date_time_set_time_title (5884883050656937853) -->
+    <skip />
+    <!-- no translation found for date_time_set_time (6449555153906058248) -->
+    <skip />
+    <!-- no translation found for date_time_set_timezone_title (3001779256157093425) -->
+    <skip />
+    <!-- no translation found for date_time_set_timezone (4759353576185916944) -->
+    <skip />
+    <!-- no translation found for date_time_set_date_title (6834785820357051138) -->
+    <skip />
+    <!-- no translation found for date_time_set_date (2537494485643283230) -->
+    <skip />
+    <!-- no translation found for zone_list_menu_sort_alphabetically (7041628618528523514) -->
+    <skip />
+    <!-- no translation found for zone_list_menu_sort_by_timezone (4944880536057914136) -->
+    <skip />
+    <!-- no translation found for date_picker_title (1533614225273770178) -->
+    <skip />
+    <!-- no translation found for time_picker_title (7436045944320504639) -->
+    <skip />
+    <!-- no translation found for user_add_user_menu (5319151436895941496) -->
+    <skip />
+    <!-- no translation found for user_add_account_menu (6625351983590713721) -->
+    <skip />
+    <!-- no translation found for user_delete_user_description (2300280525351142435) -->
+    <skip />
+    <!-- no translation found for user_new_user_name (7115771396412339662) -->
+    <skip />
+    <!-- no translation found for user_guest (3465399481257448601) -->
+    <skip />
+    <!-- no translation found for user_switch (6544839750534690781) -->
+    <skip />
+    <!-- no translation found for current_user_name (3813671533249316823) -->
+    <skip />
+    <!-- no translation found for user_name_label (3210832645046206845) -->
+    <skip />
+    <!-- no translation found for user_summary_not_set_up (1473688119241224145) -->
+    <skip />
     <string name="edit_user_name_title" msgid="6890782937520262478">"ୟୁଜର୍‍ ନାମକୁ ଏଡିଟ୍ କରନ୍ତୁ"</string>
     <string name="users_list_title" msgid="770764290290240909">"ୟୁଜର୍"</string>
     <string name="accounts_settings_title" msgid="436190037084293471">"ଆକାଉଣ୍ଟ"</string>
     <string name="user_details_title" msgid="1104762783367701498">"ୟୁଜର୍‌"</string>
     <string name="no_accounts_added" msgid="5148163140691096055">"କୌଣସି ଆକାଉଣ୍ଟ ଯୋଡ଼ାଯାଇନାହିଁ"</string>
-    <string name="account_list_title" msgid="7631588514613843065">"<xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g> ପାଇଁ ଆକାଉଣ୍ଟ"</string>
-    <string name="account_details_title" msgid="7529571432258448573">"ଆକାଉଣ୍ଟ ସୂଚନା"</string>
-    <string name="add_account_title" msgid="5988746086885210040">"ଆକାଉଣ୍ଟ ଯୋଡ଼ନ୍ତୁ"</string>
-    <string name="add_an_account" msgid="1072285034300995091">"ଗୋଟିଏ ଆକାଉଣ୍ଟ ଯୋଡ଼ନ୍ତୁ"</string>
-    <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"ପ୍ରତିବନ୍ଧିତ ପ୍ରୋଫାଇଲ୍‌ଗୁଡ଼ିକ ଆକାଉଣ୍ଟ ଆଡ୍‌ କରିପାରିବ ନାହିଁ"</string>
-    <string name="remove_account_title" msgid="8840386525787836381">"ଆକାଉଣ୍ଟ କାଢ଼ିଦିଅନ୍ତୁ"</string>
-    <string name="really_remove_account_title" msgid="3555164432587924900">"ଆକାଉଣ୍ଟ କାଢ଼ିଦେବେ?"</string>
-    <string name="really_remove_account_message" msgid="4296769280849579900">"ଏହି ଆକାଉଣ୍ଟକୁ କାଢ଼ିଦେଲେ, ଡିଭାଇସ୍‌ରେ ଥିବା ସମସ୍ତ ମେସେଜ୍‌, ଯୋଗାଯୋଗ ଓ ଅନ୍ୟାନ୍ୟ ଡାଟା ଡିଲିଟ୍‌ ହୋଇଯିବ!"</string>
-    <string name="remove_account_failed" msgid="7472511529086294087">"ଏହି ପରିବର୍ତ୍ତନ ପାଇଁ ଆପଣଙ୍କ ଆଡମିନ୍‌ ଅନୁମତି ଦେଇନାହାନ୍ତି"</string>
-    <string name="really_remove_user_title" msgid="4990029019291756762">"ଏହି ୟୁଜରଙ୍କୁ ବାହାର କରିବେ?"</string>
-    <string name="really_remove_user_message" msgid="3828090902833944533">"ସବୁ ଏପ୍ସ ଓ ଡାଟା ଉଡ଼ାଇଦିଆଯିବ"</string>
+    <!-- no translation found for account_list_title (7631588514613843065) -->
+    <skip />
+    <!-- no translation found for account_details_title (7529571432258448573) -->
+    <skip />
+    <!-- no translation found for add_account_title (5988746086885210040) -->
+    <skip />
+    <!-- no translation found for add_an_account (1072285034300995091) -->
+    <skip />
+    <!-- no translation found for user_cannot_add_accounts_message (6775605884544906797) -->
+    <skip />
+    <!-- no translation found for remove_account_title (8840386525787836381) -->
+    <skip />
+    <!-- no translation found for really_remove_account_title (3555164432587924900) -->
+    <skip />
+    <!-- no translation found for really_remove_account_message (4296769280849579900) -->
+    <skip />
+    <!-- no translation found for remove_account_failed (7472511529086294087) -->
+    <skip />
+    <!-- no translation found for really_remove_user_title (4990029019291756762) -->
+    <skip />
+    <!-- no translation found for really_remove_user_message (3828090902833944533) -->
+    <skip />
     <string name="remove_user_error_title" msgid="2038275458657689420">"ୟୁଜର୍‍ଙ୍କୁ କାଢ଼ିହେଲା ନାହିଁ।"</string>
     <string name="remove_user_error_message" msgid="6803947507134323358">"ପୁଣି ଚେଷ୍ଟା କରିବେ?"</string>
     <string name="remove_user_error_dismiss" msgid="4006591159426844335">"ଖାରଜ କରନ୍ତୁ"</string>
     <string name="remove_user_error_retry" msgid="8291692909396995093">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
-    <string name="user_add_user_title" msgid="7458813670614932479">"ନୂତନ ୟୁଜର୍‍ ଯୋଡ଼ିବେ?"</string>
-    <string name="user_add_user_message_setup" msgid="6030901156040053106">"ଜଣେ ନୂଆ ୟୁଜର୍‍ଙ୍କୁ ଯୋଡ଼ିବାବେଳେ, ସେହି ବ୍ୟକ୍ତି ତାଙ୍କ ସ୍ଥାନ ସେଟ୍‍ ଅପ୍‌ କରିବା ଆବଶ୍ୟକ କରନ୍ତି।"</string>
-    <string name="user_add_user_message_update" msgid="1528170913388932459">"ସମସ୍ତ ୟୁଜର୍‌ଙ୍କ ପାଇଁ ଯେକୌଣସି ୟୁଜର୍‌ ଆପ୍‌ ଅପଡେଟ୍‌ କରିପାରିବେ।"</string>
-    <string name="security_settings_title" msgid="6955331714774709746">"ସୁରକ୍ଷା"</string>
-    <string name="security_settings_subtitle" msgid="2244635550239273229">"ସ୍କ୍ରୀନ୍‌ ଲକ୍‌"</string>
+    <!-- no translation found for user_add_user_title (7458813670614932479) -->
+    <skip />
+    <!-- no translation found for user_add_user_message_setup (6030901156040053106) -->
+    <skip />
+    <!-- no translation found for user_add_user_message_update (1528170913388932459) -->
+    <skip />
+    <!-- no translation found for security_settings_title (6955331714774709746) -->
+    <skip />
+    <!-- no translation found for security_settings_subtitle (2244635550239273229) -->
+    <skip />
     <string name="security_lock_none" msgid="1054645093754839638">"କିଛି ନାହିଁ"</string>
-    <string name="security_lock_pattern" msgid="1174352995619563104">"ପାଟର୍ନ"</string>
+    <!-- no translation found for security_lock_pattern (1174352995619563104) -->
+    <skip />
     <string name="security_lock_pin" msgid="4891899974369503200">"PIN"</string>
-    <string name="security_lock_password" msgid="4420203740048322494">"ପାସୱର୍ଡ"</string>
-    <string name="lock_settings_picker_title" msgid="6590330165050361632">"ଏକ ଲକ୍‌ ପ୍ରକାର ବାଛନ୍ତୁ"</string>
+    <!-- no translation found for security_lock_password (4420203740048322494) -->
+    <skip />
+    <!-- no translation found for lock_settings_picker_title (6590330165050361632) -->
+    <skip />
     <string name="screen_lock_options" msgid="7023338635352915768">"ସ୍କ୍ରୀନ୍‌ ଲକ୍‌ ବିକଳ୍ପ"</string>
     <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"ଆପଣଙ୍କର ପାଟର୍ନ ଆଙ୍କନ୍ତୁ"</string>
-    <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"ନିଶ୍ଚିତ"</string>
-    <string name="lockpattern_restart_button_text" msgid="9355771277617537">"ପୁନଃଅଙ୍କନ"</string>
-    <string name="continue_button_text" msgid="5129979170426836641">"ଜାରି ରଖନ୍ତୁ"</string>
-    <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+    <!-- no translation found for lockpattern_confirm_button_text (7784925958324484965) -->
+    <skip />
+    <!-- no translation found for lockpattern_restart_button_text (9355771277617537) -->
+    <skip />
+    <!-- no translation found for continue_button_text (5129979170426836641) -->
+    <skip />
+    <!-- no translation found for lockscreen_retry_button_text (5314212350698701242) -->
+    <skip />
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"ଛାଡ଼ିଦିଅନ୍ତୁ"</string>
-    <string name="set_screen_lock" msgid="5239317292691332780">"ଏକ ସ୍କ୍ରିନ୍‌ ଲକ୍‌ ସେଟ୍ କରନ୍ତୁ"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"ନିଜ ପିନ୍‌ ନମ୍ବର ବାଛନ୍ତୁ"</string>
+    <!-- no translation found for set_screen_lock (5239317292691332780) -->
+    <skip />
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"ନିଜ ପାର୍ଟନ୍‌ ବାଛନ୍ତୁ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"ନିଜ ପାସ୍‌ୱର୍ଡ ବାଛନ୍ତୁ"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"ବର୍ତ୍ତମାନର ସ୍କ୍ରୀନ୍‌ ଲକ୍‌"</string>
-    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"ସୁରକ୍ଷା ପାଇଁ ଗୋଟିଏ ପାଟର୍ନ ସେଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"ଖାଲି କରନ୍ତୁ"</string>
-    <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"କ୍ୟାନ୍ସଲ୍‍ କରନ୍ତୁ"</string>
-    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"ଆପଣଙ୍କର ନୂଆ ଅନ୍‌ଲକ୍‌ ପାଟର୍ନ"</string>
-    <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"ଏକ ଅନଲକ ପାଟର୍ନ ଆଙ୍କନ୍ତୁ"</string>
-    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"ହୋଇଯିବା ପରେ ଆଙ୍ଗୁଠି କାଢ଼ିଦିଅନ୍ତୁ"</string>
-    <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"ପାଟର୍ନ ରେକର୍ଡ ହେଲା"</string>
-    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"ନିଶ୍ଚିତ କରିବାକୁ ପାଟର୍ନ ପୁନଃ ଅଙ୍କନ କରନ୍ତୁ"</string>
-    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"ଅତିକମ୍‌ରେ 4ଟି ଡଟ୍ ସହ ସଂଯୋଗ କରନ୍ତୁ। ପୁଣିଥରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
-    <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"ଭୁଲ ପାଟର୍ନ"</string>
-    <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"ଏକ ଅନ୍‌ଲକ୍‌ ପାଟର୍ନ କିପରି ଅଙ୍କାଯିବ"</string>
-    <string name="error_saving_lockpattern" msgid="2933512812768570130">"ପାଟର୍ନ ସେଭ୍‌ କରିବାରେ ତ୍ରୁଟି"</string>
-    <string name="okay" msgid="4589873324439764349">"ଓକେ"</string>
+    <!-- no translation found for choose_lock_pattern_message (6242765203541309524) -->
+    <skip />
+    <!-- no translation found for lockpattern_retry_button_text (4655398824001857843) -->
+    <skip />
+    <!-- no translation found for lockpattern_cancel_button_text (4068764595622381766) -->
+    <skip />
+    <!-- no translation found for lockpattern_pattern_confirmed (5984306638250515385) -->
+    <skip />
+    <!-- no translation found for lockpattern_recording_intro_header (7864149726033694408) -->
+    <skip />
+    <!-- no translation found for lockpattern_recording_inprogress (1575019990484725964) -->
+    <skip />
+    <!-- no translation found for lockpattern_pattern_entered (6103071005285320575) -->
+    <skip />
+    <!-- no translation found for lockpattern_need_to_confirm (4648070076022940382) -->
+    <skip />
+    <!-- no translation found for lockpattern_recording_incorrect_too_short (2417932185815083082) -->
+    <skip />
+    <!-- no translation found for lockpattern_pattern_wrong (929223969555399363) -->
+    <skip />
+    <!-- no translation found for lockpattern_settings_help_how_to_record (4436556875843192284) -->
+    <skip />
+    <!-- no translation found for error_saving_lockpattern (2933512812768570130) -->
+    <skip />
+    <!-- no translation found for okay (4589873324439764349) -->
+    <skip />
     <string name="remove_screen_lock_title" msgid="1234382338764193387">"ସ୍କ୍ରୀନ ଲକ୍ ରିମୁଭ୍‍ କରିବେ କି?"</string>
     <string name="remove_screen_lock_message" msgid="6675850371585564965">"ଏହା ଯେକୌଣସି ବ୍ୟକ୍ତି ଆପଣଙ୍କ ଆକାଉଣ୍ଟରେ ପ୍ରବେଶ ପାଇଁ ଅନୁମତି ଦେବ"</string>
     <string name="lock_settings_enter_pin" msgid="1669172111244633904">"ନିଜର PIN ଲେଖନ୍ତୁ"</string>
     <string name="lock_settings_enter_password" msgid="2636669926649496367">"ନିଜ ପାସ୍‌ୱର୍ଡ ଲେଖନ୍ତୁ"</string>
-    <string name="choose_lock_pin_message" msgid="2963792070267774417">"ସୁରକ୍ଷା ପାଇଁ, ଏକ PIN ସେଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="confirm_your_pin_header" msgid="9096581288537156102">"ନିଜ PIN ପୁଣି ପ୍ରବେଶ କରନ୍ତୁ"</string>
-    <string name="choose_lock_pin_hints" msgid="7362906249992020844">"PINରେ ଅତିକମ୍‌ରେ 4ଟିଅଙ୍କ ରହିବା ଦରକାର"</string>
+    <!-- no translation found for choose_lock_pin_message (2963792070267774417) -->
+    <skip />
+    <!-- no translation found for confirm_your_pin_header (9096581288537156102) -->
+    <skip />
+    <!-- no translation found for choose_lock_pin_hints (7362906249992020844) -->
+    <skip />
     <string name="lockpin_invalid_pin" msgid="2149191577096327424">"Pin ଅମାନ୍ୟ ଅଟେ, ଅତିକମରେ 4ଟି ସଂଖ୍ୟା ରହିବା ଆବଶ୍ୟକ।"</string>
-    <string name="confirm_pins_dont_match" msgid="4607110139373520720">"PINଗୁଡିକ ମେଳ ଖାଉ ନାହିଁ"</string>
-    <string name="error_saving_lockpin" msgid="9011960139736000393">"PIN ସେଭ୍‌ କରିବାରେ ତ୍ରୁଟି"</string>
+    <!-- no translation found for confirm_pins_dont_match (4607110139373520720) -->
+    <skip />
+    <!-- no translation found for error_saving_lockpin (9011960139736000393) -->
+    <skip />
     <string name="lockscreen_wrong_pin" msgid="4922465731473805306">"ଭୁଲ PIN"</string>
     <string name="lockscreen_wrong_password" msgid="5757087577162231825">"ଭୁଲ ପାସ୍‌ୱର୍ଡ"</string>
-    <string name="choose_lock_password_message" msgid="6124341145027370784">"ସୁରକ୍ଷା ପାଇଁ ଗୋଟିଏ ପାସ୍‌ୱର୍ଡ ସେଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="confirm_your_password_header" msgid="7052891840366724938">"ପାସ୍‌ୱର୍ଡ ପୁନଃ-ପ୍ରବେଶ କରନ୍ତୁ"</string>
-    <string name="confirm_passwords_dont_match" msgid="7300229965206501753">"ପାସ୍‌ୱର୍ଡ ମେଳ ଖାଉନାହିଁ"</string>
-    <string name="lockpassword_clear_label" msgid="6363680971025188064">"ଖାଲି କରନ୍ତୁ"</string>
-    <string name="lockpassword_cancel_label" msgid="5791237697404166450">"କ୍ୟାନ୍ସଲ୍‍ କରନ୍ତୁ"</string>
-    <string name="lockpassword_confirm_label" msgid="5918463281546146953">"ନିଶ୍ଚିତ"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"ଅତିକମ୍‌ରେ 4 ଅକ୍ଷର ରହିବା ଦରକାର"</string>
-    <string name="lockpassword_password_too_short" msgid="6681218025001328405">"ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g> ଅକ୍ଷର ରହିବା ଦରକାର"</string>
-    <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g> ଅଙ୍କ ବିଶିଷ୍ଟ ହେବା ଦରକାର"</string>
-    <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> ଅକ୍ଷରଠାରୁ କମ୍‌ ହେବା ଦରକାର"</string>
-    <string name="lockpassword_pin_too_long" msgid="62957683396974404">"<xliff:g id="NUMBER">%d</xliff:g> ଅଙ୍କରୁ କମ୍‌ ରହିବା ଦରକାର"</string>
+    <!-- no translation found for choose_lock_password_message (6124341145027370784) -->
+    <skip />
+    <!-- no translation found for confirm_your_password_header (7052891840366724938) -->
+    <skip />
+    <!-- no translation found for confirm_passwords_dont_match (7300229965206501753) -->
+    <skip />
+    <!-- no translation found for lockpassword_clear_label (6363680971025188064) -->
+    <skip />
+    <!-- no translation found for lockpassword_cancel_label (5791237697404166450) -->
+    <skip />
+    <!-- no translation found for lockpassword_confirm_label (5918463281546146953) -->
+    <skip />
+    <!-- no translation found for choose_lock_password_hints (1802962836351866087) -->
+    <skip />
+    <!-- no translation found for lockpassword_password_too_short (6681218025001328405) -->
+    <skip />
+    <!-- no translation found for lockpassword_pin_too_short (6363004004424904218) -->
+    <skip />
+    <!-- no translation found for lockpassword_password_too_long (7530214940279491291) -->
+    <skip />
+    <!-- no translation found for lockpassword_pin_too_long (62957683396974404) -->
+    <skip />
     <string name="lockpassword_pin_contains_non_digits" msgid="3044526271686839923">"କେବଳ 0-9 ପର୍ଯ୍ୟନ୍ତ ସଂଖ୍ୟା ରହିବା ଦରକାର"</string>
-    <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ଏକ ସାମ୍ପ୍ରତିକ ପିନ୍‌ର ବ୍ୟବହାରକୁ ଡିଭାଇସ୍‌ ଆଡ୍‌ମିନ୍‌ ଅନୁମତି ଦେବନାହିଁ"</string>
-    <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ସାଧାରଣ PINଗୁଡିକ ଆପଣଙ୍କ IT ଆଡମିନ୍‌ଙ୍କ ଦ୍ୱାରା ଅବରୋଧ କରିଦିଆଯାଇଛି। ଭିନ୍ନ PIN ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+    <!-- no translation found for lockpassword_pin_recently_used (7901918311213276207) -->
+    <skip />
+    <!-- no translation found for lockpassword_pin_blacklisted_by_admin (7412709707800738442) -->
+    <skip />
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ଏହା ଗୋଟିଏ ଭୁଲ ଅକ୍ଷରକୁ ଯୋଡ଼ିପାରିବନାହିଁ"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"ପାସ୍‍ୱର୍ଡ ଭୁଲ ଅଟେ, ଅତିକମ୍‌ରେ 4 ଅକ୍ଷର ରହିବା ଦରକାର।"</string>
-    <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
-      <item quantity="other">ଅତିକମରେ <xliff:g id="COUNT">%d</xliff:g>ଟି ଅକ୍ଷର ରହିବା ଦରକାର</item>
-      <item quantity="one">ଅତିକମରେ ୧ଟି ଅକ୍ଷର ରହିବା ଦରକାର</item>
-    </plurals>
-    <plurals name="lockpassword_password_requires_lowercase" formatted="false" msgid="2267487180744744833">
-      <item quantity="other">ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g> ଟି ଲୋୱର୍‌କେସ୍‌ ଅକ୍ଷର ରହିବା ଦରକାର</item>
-      <item quantity="one">ଅତିକମ୍‌ରେ 1ଟି ଲୋୱର୍‍କେସ୍ ଅକ୍ଷର ରହିବା ଦରକାର</item>
-    </plurals>
-    <plurals name="lockpassword_password_requires_uppercase" formatted="false" msgid="7999264563026517898">
-      <item quantity="other">ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g>ଟି ବଡ଼ ଅକ୍ଷର ଆବଶ୍ୟକ</item>
-      <item quantity="one"> ଅତିକମ୍‌ରେ 1ଟି ବଡ଼ ଅକ୍ଷର ଆବଶ୍ୟକ</item>
-    </plurals>
-    <plurals name="lockpassword_password_requires_numeric" formatted="false" msgid="7935079851855168646">
-      <item quantity="other">ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g>ଟି ସଂଖ୍ୟା ନିହାତି ରହିବା ଦରକାର</item>
-      <item quantity="one">ଅତିକମ୍‌ରେ 1ଟି ସଂଖ୍ୟା ନିହାତି ରହିବା ଦରକାର</item>
-    </plurals>
-    <plurals name="lockpassword_password_requires_symbols" formatted="false" msgid="3994046435150094132">
-      <item quantity="other">ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g> ଟି ବିଶେଷ ସଙ୍କେତ ରହିଥିବା ଜରୁରୀ</item>
-      <item quantity="one">ଅତିକମ୍‌ରେ 1 ବିଶେଷ ସଙ୍କେତ ରହିଥିବା ଜରୁରୀ</item>
-    </plurals>
-    <plurals name="lockpassword_password_requires_nonletter" formatted="false" msgid="6878486326748506524">
-      <item quantity="other">ଅତିକମ୍‌ରେ <xliff:g id="COUNT">%d</xliff:g>ଟି ବିଶେଷ ଅକ୍ଷର କିମ୍ୱା ସଂଖ୍ୟା ଆବଶ୍ୟକ</item>
-      <item quantity="one">ଅତିକମ୍‌ରେ 1ଟି ବିଶେଷ ଅକ୍ଷର କିମ୍ୱା ସଂଖ୍ୟା ଆବଶ୍ୟକ</item>
-    </plurals>
-    <string name="lockpassword_password_recently_used" msgid="8255729487108602924">"ଏକ ସମ୍ପ୍ରତି ପାସ୍‌ୱର୍ଡ ବ୍ୟବହାର କରିବାକୁ ଡିଭାଇସ୍‌ ଆଡମିନ୍‌ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ"</string>
-    <string name="error_saving_password" msgid="8334882262622500658">"ପାସୱର୍ଡ ସେଭ୍‌ କରିବାରେ ତ୍ରୁଟି"</string>
-    <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"ଆପଣଙ୍କର ଆଇଟି ଆଡ୍‌ମିନ୍‌ ଦ୍ୱାରା ସାଧାରଣ ପାସ୍‌ୱର୍ଡଗୁଡ଼ିକୁ ରୋକିଦିଆଯାଇଛି। ଏକ ଭିନ୍ନ ପାସ୍‌ୱର୍ଡ ଦିଅନ୍ତୁ।"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"ପାସ୍‌ୱର୍ଡ ଅମାନ୍ୟ ଅଟେ, ଏଥିରେ ନିହାତି 4-8ଟି ବର୍ଣ୍ଣ ରହିବା ଦରକାର, ଯେଉଁଥିରେ ଅତିକମରେ 1ଟି ସଂଖ୍ୟା, 1 ଅକ୍ଷର ଥିବ ଓ କୌଣସି ଖାଲି ସ୍ଥାନ ନଥିବ।"</string>
+    <!-- no translation found for lockpassword_password_requires_letters (424616259312760303) -->
+    <!-- no translation found for lockpassword_password_requires_lowercase (2267487180744744833) -->
+    <!-- no translation found for lockpassword_password_requires_uppercase (7999264563026517898) -->
+    <!-- no translation found for lockpassword_password_requires_numeric (7935079851855168646) -->
+    <!-- no translation found for lockpassword_password_requires_symbols (3994046435150094132) -->
+    <!-- no translation found for lockpassword_password_requires_nonletter (6878486326748506524) -->
+    <!-- no translation found for lockpassword_password_recently_used (8255729487108602924) -->
+    <skip />
+    <!-- no translation found for error_saving_password (8334882262622500658) -->
+    <skip />
+    <!-- no translation found for lockpassword_password_blacklisted_by_admin (7965893810326503891) -->
+    <skip />
     <string name="lockpassword_pin_no_sequential_digits" msgid="38813552228809240">"ସଂଖ୍ୟାର କ୍ରମବୃଦ୍ଧି, କ୍ରମହ୍ରାସ, କିମ୍ବା କ୍ରମ ଦୋହରାଇବାର ଅନୁମତି ନାହିଁ।"</string>
-    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"ସ୍କ୍ରୀନ୍‌ ଲକ୍‌ ବିକଳ୍ପମାନ"</string>
-    <string name="forget" msgid="3971143908183848527">"ଭୁଲିଗଲେ"</string>
-    <string name="delete_button" msgid="5840500432614610850">"ଡିଲିଟ୍‍"</string>
-    <string name="remove_button" msgid="6664656962868194178">"କାଢ଼ିଦିଅନ୍ତୁ"</string>
-    <string name="cancel" msgid="750286395700355455">"କ୍ୟାନ୍ସଲ୍‍ କରନ୍ତୁ"</string>
+    <!-- no translation found for setup_lock_settings_options_button_label (3337845811029780896) -->
+    <skip />
+    <!-- no translation found for forget (3971143908183848527) -->
+    <skip />
+    <!-- no translation found for delete_button (5840500432614610850) -->
+    <skip />
+    <!-- no translation found for remove_button (6664656962868194178) -->
+    <skip />
+    <!-- no translation found for cancel (750286395700355455) -->
+    <skip />
     <string name="backspace_key" msgid="1545590866688979099">"Backspace କୀ"</string>
     <string name="enter_key" msgid="2121394305541579468">"କୀ ଦବାନ୍ତୁ"</string>
     <string name="exit_retail_button_text" msgid="6093240315583384473">"ଡେମୋରୁ ବାହାରିଯା’ନ୍ତୁ"</string>
@@ -300,6 +476,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ଡେମୋରୁ ବାହାରିଯା’ନ୍ତୁ"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ସେଟ୍-ଅପ୍ ସମାପ୍ତ କରନ୍ତୁ"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ଏବେ ନୁହେଁଁ"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ଗାଡ଼ି ଚଲାଇବାବେଳେ ଫିଚର୍ ଉପଲବ୍ଧ ହେବନାହିଁ।"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ଗାଡ଼ି ଚଲାଇବା ବେଳେ ୟୁଜର୍ ଯୋଡ଼ିପାରିବେ ନାହିଁ।"</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 3a2d03f..aa1b6ac 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"ਜੋੜਾਬੱਧ ਕਰਨ ਦੀ ਬੇਨਤੀ"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਨਾਲ ਜੋੜਾ ਬਣਾਉਣ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ਭਾਸ਼ਾਵਾਂ"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ਧੁਨੀ"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"ਰਿੰਗ ਵੌਲਿਊਮ"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"ਨੈਵੀਗੇਸ਼ਨ ਵੌਲਿਊਮ"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ਵਰਤੋਂਕਾਰ ਨੂੰ ਮਿਟਾਓ"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ"</string>
     <string name="user_guest" msgid="3465399481257448601">"ਮਹਿਮਾਨ"</string>
-    <string name="user_admin" msgid="1535484812908584809">"ਪ੍ਰਸ਼ਾਸਕ"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"ਪ੍ਰਸ਼ਾਸਕ ਵਜੋਂ ਸਾਈਨ ਇਨ ਕੀਤਾ ਗਿਆ"</string>
     <string name="user_switch" msgid="6544839750534690781">"ਅਦਲਾ-ਬਦਲੀ ਕਰੋ"</string>
     <string name="current_user_name" msgid="3813671533249316823">"ਤੁਸੀਂ (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"ਨਾਮ"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"ਛੱਡੋ"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"ਕੋਈ ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਕਰੋ"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"ਆਪਣਾ ਪਿੰਨ ਚੁਣੋ"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"ਆਪਣਾ ਪੈਟਰਨ ਚੁਣੋ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"ਆਪਣਾ ਪਾਸਵਰਡ ਚੁਣੋ"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"ਮੌਜੂਦਾ ਸਕ੍ਰੀਨ ਲਾਕ"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"ਸੁਰੱਖਿਆ ਲਈ, ਕੋਈ ਪੈਟਰਨ ਸੈੱਟ ਕਰੋ"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"ਕਲੀਅਰ ਕਰੋ"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"ਰੱਦ ਕਰੋ"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"ਪੁਸ਼ਟੀ ਕਰੋ"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"ਘੱਟੋ-ਘੱਟ 4 ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਦਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"ਪਾਸਵਰਡ ਘੱਟੋ-ਘੱਟ 1 ਅੰਕ ਨਾਲ 4-8 ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਦੇ ਵਿਚਕਾਰ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"ਘੱਟੋ-ਘੱਟ <xliff:g id="COUNT">%d</xliff:g> ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਦਾ ਹੋਣਾ ਚਾਹੀਦਾ"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"ਪਿੰਨ ਘੱਟੋ-ਘੱਟ <xliff:g id="COUNT">%d</xliff:g> ਅੰਕਾਂ ਦਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਤੋਂ ਘੱਟ ਦਾ ਹੋਣਾ ਚਾਹੀਦਾ"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ਡੀਵਾਈਸ ਪ੍ਰਸ਼ਾਸਕ ਇੱਕ ਹਾਲੀਆ ਪਿੰਨ ਵਰਤਣ ਨਹੀਂ ਦਿੰਦਾ ਹੈ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ਆਮ ਪਿੰਨ ਤੁਹਾਡੇ IT ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤੇ ਗਏ ਹਨ। ਕੋਈ ਵੱਖਰਾ ਪਿੰਨ ਵਰਤੋ।"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ਇਸ ਵਿੱਚ ਇੱਕ ਅਵੈਧ ਅੱਖਰ-ਚਿੰਨ੍ਹ ਸ਼ਾਮਲ ਨਹੀਂ ਹੋ ਸਕਦਾ।"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"ਪਾਸਵਰਡ ਵੈਧ ਹੈ, ਘੱਟੋ-ਘੱਟ 4 ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਦਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"ਪਾਸਵਰਡ ਅਵੈਧ ਹੈ, ਇਹ 4-8 ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਦਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਜਿਸ ਵਿੱਚ ਘੱਟੋ-ਘੱਟ 1 ਅੰਕ, 1 ਅੱਖਰ ਹੋਵੇ ਅਤੇ ਕੋਈ ਖਾਲੀ ਜਗ੍ਹਾ ਸ਼ਾਮਲ ਨਾ ਹੋਵੇ।"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">ਘੱਟੋ-ਘੱਟ <xliff:g id="COUNT">%d</xliff:g> ਅੱਖਰ ਸ਼ਾਮਲ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ</item>
       <item quantity="other">ਘੱਟੋ-ਘੱਟ <xliff:g id="COUNT">%d</xliff:g> ਅੱਖਰ ਸ਼ਾਮਲ ਕਰਨੇ ਲਾਜ਼ਮੀ ਹਨ</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ਡੈਮੋ ਮੋਡ ਤੋ ਬਾਹਰ ਜਾਓ"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ਸੈੱਟਅੱਪ ਪੂਰਾ ਕਰੋ"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ਹੁਣੇ ਨਹੀਂ"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ਗੱਡੀ ਚਲਾਉਣ ਵੇਲੇ ਵਿਸ਼ੇਸ਼ਤਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ਗੱਡੀ ਚਲਾਉਣ ਦੌਰਾਨ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।"</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 3ac30f3..4910ec2 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Prośba o sparowanie"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Dotknij, by sparować z: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Języki"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Dźwięk"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Głośność dzwonka"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Głośność nawigacji"</string>
@@ -151,10 +150,10 @@
     <string name="date_and_time_settings_title" msgid="4058492663544475485">"Data i godzina"</string>
     <string name="date_and_time_settings_title_setup_wizard" msgid="7580119979694174107">"Ustaw datę i godzinę"</string>
     <string name="date_and_time_settings_summary" msgid="7669856855390804666">"Ustaw datę, godzinę, strefę czasową i formaty."</string>
-    <string name="date_time_auto" msgid="3570339569471779767">"Automatyczna data i godzina"</string>
+    <string name="date_time_auto" msgid="3570339569471779767">"Automatyczna data i czas"</string>
     <string name="date_time_auto_summary" msgid="3311706425095342759">"Użyj czasu podanego przez sieć"</string>
     <string name="zone_auto" msgid="3701878581920206160">"Automatyczna strefa czasowa"</string>
-    <string name="zone_auto_summary" msgid="4345856882906981864">"Użyj strefy czasowej podanej przez sieć"</string>
+    <string name="zone_auto_summary" msgid="4345856882906981864">"Użyj strefy czasowej podanej przez sieć"</string>
     <string name="date_time_24hour_title" msgid="3025576547136168692">"Format 24-godzinny"</string>
     <string name="date_time_24hour" msgid="1137618702556486913">"Format 24-godzinny"</string>
     <string name="date_time_set_time_title" msgid="5884883050656937853">"Czas"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Usuwanie użytkownika"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nowy użytkownik"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gość"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Zalogowano jako administratora"</string>
     <string name="user_switch" msgid="6544839750534690781">"Przełącz"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Ty (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nazwa"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Ponów"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Pomiń"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ustawianie blokady ekranu"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Ustaw kod PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Wybierz wzór"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Ustaw hasło"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Aktualna blokada ekranu"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Ze względów bezpieczeństwa ustaw wzór"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Wyczyść"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Wyczyść"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Anuluj"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Potwierdź"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Musisz użyć co najmniej czterech znaków"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Hasło: 4–8 znaków, co najmniej 1 cyfra"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Minimalna liczba znaków to <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Minimalna liczba cyfr w kodzie PIN to <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Musi mieć mniej znaków niż <xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Administrator urządzenia nie zezwala na ustawianie niedawno używanego kodu PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Popularne kody PIN zostały zablokowane przez administratora. Użyj innego kodu PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Nie może zawierać nieprawidłowego znaku."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Hasło jest nieprawidłowe. Musisz użyć co najmniej czterech znaków."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Hasło jest nieprawidłowe. Musi mieć od 4 do 8 znaków i zawierać co najmniej jedną cyfrę i jedną literę, bez spacji."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="few">Musi zawierać co najmniej <xliff:g id="COUNT">%d</xliff:g> litery</item>
       <item quantity="many">Musi zawierać co najmniej <xliff:g id="COUNT">%d</xliff:g> liter</item>
@@ -315,5 +310,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"dokończ konfigurację"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"nie teraz"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funkcja niedostępna podczas jazdy."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Nie można dodać użytkownika podczas jazdy."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index f2224e3..f0262fa 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Pedido de sincronização"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Toque para sincronizar com o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Idiomas"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Som"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volume do toque"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volume da navegação"</string>
@@ -108,7 +107,7 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Nenhuma autorização concedida."</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Nenhuma autorização solicitada."</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Utilização de dados"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Utiliz. dados da app"</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Utiliz. dados da aplic."</string>
     <string name="force_stop" msgid="2153183697014720520">"Forçar paragem"</string>
     <string name="computing_size" msgid="5791407621793083965">"A calcular…"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Eliminar utilizador"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Novo utilizador"</string>
     <string name="user_guest" msgid="3465399481257448601">"Convidado"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrador"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Sessão iniciada como administrador"</string>
     <string name="user_switch" msgid="6544839750534690781">"Mudar"</string>
     <string name="current_user_name" msgid="3813671533249316823">"O utilizador (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nome"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Tentar novamente"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Ignorar"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Definir um bloqueio de ecrã"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Escolher o PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Escolher o padrão"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Escolher a palavra-passe"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Bloqueio de ecrã atual"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Por segurança, defina um padrão."</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Limpar"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Limpar"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancelar"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirmar"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Tem de ter, pelo menos, 4 carateres."</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"A palavra-passe tem de ter entre 4 e 8 carateres com, pelo menos, 1 número."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Tem de ter, pelo menos, <xliff:g id="COUNT">%d</xliff:g> carateres."</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"O PIN tem de ter, pelo menos, <xliff:g id="COUNT">%d</xliff:g> dígitos."</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Tem de ter menos de <xliff:g id="NUMBER">%d</xliff:g> carateres."</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"O administrador do dispositivo não permite a utilização de um PIN recente."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Os PINs comuns estão bloqueados pelo seu gestor de TI. Experimente outro PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Não pode incluir um caráter inválido."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Palavra-passe inválida. Tem de ter, pelo menos, 4 carateres."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Palavra-passe inválida. Tem de ter entre 4 e 8 carateres, incluir, pelo menos, 1 dígito, 1 letra e nenhum espaço em branco."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Tem de incluir, pelo menos, <xliff:g id="COUNT">%d</xliff:g> letras.</item>
       <item quantity="one">Tem de incluir, pelo menos, 1 letra.</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"concluir configuração"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"agora não"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funcionalidade não disponível durante a condução."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Não é possível adicionar o utilizador durante a condução."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 184ee4c..f6c1c2a 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Solicitação de pareamento"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Toque para parear com <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Idiomas"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Som"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volume do toque"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volume de navegação"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Excluir usuário"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Novo usuário"</string>
     <string name="user_guest" msgid="3465399481257448601">"Convidado"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrador"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Conectado como administrador"</string>
     <string name="user_switch" msgid="6544839750534690781">"Alternar"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Você (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nome"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Repetir"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Pular"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Definir bloqueio de tela"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Escolha seu PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Escolha seu padrão"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Escolha sua senha"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Bloqueio de tela atual"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Por segurança, defina um padrão"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Limpar"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Limpar"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Cancelar"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirmar"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Precisa ter pelo menos quatro caracteres"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"A senha precisa ter entre 4 e 8 caracteres com pelo menos 1 número"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Precisa ter pelo menos <xliff:g id="COUNT">%d</xliff:g> caracteres"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"O PIN precisa ter pelo menos <xliff:g id="COUNT">%d</xliff:g> dígitos"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Precisa ter menos de <xliff:g id="NUMBER">%d</xliff:g> caracteres"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"O administrador do dispositivo não permite o uso de um PIN recente"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"PINs comuns foram bloqueados pelo seu administrador de TI. Tente um PIN diferente."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Não pode incluir um caractere inválido."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Senha inválida. Ela precisa ter pelo menos quatro caracteres."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Senha inválida. Ela precisa ter entre quatro e oito caracteres, conter pelo menos um número, uma letra e nenhum espaço."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Precisa ter pelo menos <xliff:g id="COUNT">%d</xliff:g> letra</item>
       <item quantity="other">Precisa ter pelo menos <xliff:g id="COUNT">%d</xliff:g> letras</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"concluir configuração"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"agora não"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Recurso não disponível enquanto você dirige."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Não é possível adicionar usuários enquanto você dirige."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index dd85269..9e07304 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Solicitare de conectare"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Atingeți pentru a asocia cu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Limbi"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Sunet"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volumul soneriei"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volum navigare"</string>
@@ -153,7 +152,7 @@
     <string name="date_time_auto" msgid="3570339569471779767">"Dată și oră automate"</string>
     <string name="date_time_auto_summary" msgid="3311706425095342759">"Folosiți ora dată de rețea"</string>
     <string name="zone_auto" msgid="3701878581920206160">"Fus orar automat"</string>
-    <string name="zone_auto_summary" msgid="4345856882906981864">"Folosiți fusul orar din rețea"</string>
+    <string name="zone_auto_summary" msgid="4345856882906981864">"Folosește fusul orar din rețea"</string>
     <string name="date_time_24hour_title" msgid="3025576547136168692">"Format de 24 de ore"</string>
     <string name="date_time_24hour" msgid="1137618702556486913">"Utilizați formatul de 24 de ore"</string>
     <string name="date_time_set_time_title" msgid="5884883050656937853">"Ora"</string>
@@ -171,8 +170,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Ștergeți utilizatorul"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Utilizator nou"</string>
     <string name="user_guest" msgid="3465399481257448601">"Invitat"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Conectat(ă) ca administrator"</string>
     <string name="user_switch" msgid="6544839750534690781">"Comutați"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Dvs. (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Nume"</string>
@@ -215,9 +212,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Reîncercați"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Omiteți"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Setați o blocare a ecranului"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Alegeți codul PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Alegeți modelul"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Alegeți parola"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Blocarea actuală a ecranului"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Pentru securitate, setați un model"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Ștergeți"</string>
@@ -250,7 +245,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Ștergeți"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Anulați"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Confirmați"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Trebuie să conțină minimum 4 caractere"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Parola necesită 4–8 caractere și o cifră"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Trebuie să aibă minimum <xliff:g id="COUNT">%d</xliff:g> caractere"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Codul PIN trebuie să aibă minimum <xliff:g id="COUNT">%d</xliff:g> cifre"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Trebuie să aibă maximum <xliff:g id="NUMBER">%d</xliff:g> caractere"</string>
@@ -259,7 +254,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Administratorul dispozitivului nu permite folosirea unui PIN recent"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Codurile PIN obișnuite sunt blocate de administratorul IT. Încercați alt cod PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Aceasta nu poate include un caracter nevalid."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Parolă nevalidă. Trebuie să conțină minimum 4 caractere."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Parola este nevalidă; trebuie să aibă 4 – 8 caractere, să conțină cel puțin o cifră, o literă și niciun spațiu alb."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="few">Trebuie să conțină cel puțin <xliff:g id="COUNT">%d</xliff:g> litere</item>
       <item quantity="other">Trebuie să conțină cel puțin <xliff:g id="COUNT">%d</xliff:g> de litere</item>
@@ -308,5 +303,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"finalizați configurarea"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"nu acum"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funcția nu este disponibilă când conduceți."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Nu puteți adăuga un utilizator în timp ce conduceți."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 8415291..965a2f1 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -77,12 +77,12 @@
     <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"Использовать для"</string>
     <string name="wifi_ssid_hint" msgid="4155050863239489553">"Измените имя Bluetooth-устройства"</string>
     <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"Запрос на подключение через Bluetooth"</string>
-    <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"Установить соединение и подключить"</string>
+    <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"Выполнить сопряжение и подключить"</string>
     <string name="bluetooth" msgid="5235115159234688629">"Bluetooth"</string>
     <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"Код подключения через Bluetooth"</string>
     <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"PIN-код содержит буквы или символы"</string>
     <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"Введите код подключения и нажмите клавишу ввода"</string>
-    <string name="bluetooth_pairing_request" msgid="4769675459526556801">"Установить соединение с устройством <xliff:g id="DEVICE_NAME">%1$s</xliff:g>?"</string>
+    <string name="bluetooth_pairing_request" msgid="4769675459526556801">"Подключиться к устройству <xliff:g id="DEVICE_NAME">%1$s</xliff:g>?"</string>
     <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"Разрешить устройству <xliff:g id="DEVICE_NAME">%1$s</xliff:g> доступ к списку контактов и журналу звонков"</string>
     <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"Возможно, потребуется ввести PIN-код на другом устройстве."</string>
     <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"Возможно, потребуется ввести ключ доступа на другом устройстве."</string>
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Запрос на подключение"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Нажмите, чтобы установить соединение с устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Языки"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Звук"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Громкость звонка"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Громкость навигации"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Удалить пользователя"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Новый пользователь"</string>
     <string name="user_guest" msgid="3465399481257448601">"Гость"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Администратор"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Вы вошли как администратор"</string>
     <string name="user_switch" msgid="6544839750534690781">"Сменить пользователя"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Вы (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Имя"</string>
@@ -208,7 +205,7 @@
     <string name="security_lock_pin" msgid="4891899974369503200">"PIN-код"</string>
     <string name="security_lock_password" msgid="4420203740048322494">"Пароль"</string>
     <string name="lock_settings_picker_title" msgid="6590330165050361632">"Выберите тип блокировки"</string>
-    <string name="screen_lock_options" msgid="7023338635352915768">"Варианты блокировки экрана"</string>
+    <string name="screen_lock_options" msgid="7023338635352915768">"Параметры блокировки экрана"</string>
     <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"Введите графический ключ"</string>
     <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"Подтвердить"</string>
     <string name="lockpattern_restart_button_text" msgid="9355771277617537">"Начертить ещё раз"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Повторить попытку"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Пропустить"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Блокировка экрана"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Выберите PIN-код"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Выберите графический ключ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Выберите пароль"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Текущий способ блокировки"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Для защиты системы создайте граф. ключ."</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Очистить"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Очистить"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Отмена"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Подтвердить"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Не менее 4 символов"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Пароль должен содержать от 4 до 8 символов, в т. ч. не менее 1 цифры."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Пароль должен быть не короче <xliff:g id="COUNT">%d</xliff:g> симв."</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN-код должен содержать не менее <xliff:g id="COUNT">%d</xliff:g> цифр"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Пароль должен быть короче <xliff:g id="NUMBER">%d</xliff:g> симв."</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Использовать недавний PIN-код запрещено"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ИТ-администратор запретил использовать простые PIN-коды. Выберите более сложную комбинацию."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Использованы недопустимые символы."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Пароль должен содержать не менее четырех символов."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Ошибка. Пароль должен содержать от 4 до 8 символов, в том числе не менее 1 цифры и 1 буквы. Использовать пробелы нельзя."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Пароль должен содержать не менее <xliff:g id="COUNT">%d</xliff:g> буквы.</item>
       <item quantity="few">Пароль должен содержать не менее <xliff:g id="COUNT">%d</xliff:g> букв.</item>
@@ -301,7 +296,7 @@
     <string name="error_saving_password" msgid="8334882262622500658">"Не удалось сохранить пароль"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"ИТ-администратор запретил использовать простые пароли. Выберите более сложную комбинацию."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="38813552228809240">"Нельзя использовать последовательности из идущих подряд или повторяющихся цифр."</string>
-    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"Варианты блокировки экрана"</string>
+    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"Параметры блокировки экрана"</string>
     <string name="forget" msgid="3971143908183848527">"Удалить"</string>
     <string name="delete_button" msgid="5840500432614610850">"Удалить"</string>
     <string name="remove_button" msgid="6664656962868194178">"Удалить"</string>
@@ -315,5 +310,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"Завершить настройку"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"Не сейчас"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Эта функция недоступна во время вождения."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Невозможно добавить пользователя во время вождения."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 3d06bf6..cc247db 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"යුගල කිරීමේ ඉල්ලීම"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> සමඟ යුගල කිරීමට තට්ටු කරන්න."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"භාෂා"</string>
     <string name="sound_settings" msgid="3072423952331872246">"හඬ"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"නාද ශබ්දය"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"සංචාලන ශබ්දය"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"පරිශීලකයා මකන්න"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"නව පරිශීලකයා"</string>
     <string name="user_guest" msgid="3465399481257448601">"අමුත්තා"</string>
-    <string name="user_admin" msgid="1535484812908584809">"පරිපාලක"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"පරිපාලක ලෙස පුරනය වී ඇත"</string>
     <string name="user_switch" msgid="6544839750534690781">"මාරු වෙන්න"</string>
     <string name="current_user_name" msgid="3813671533249316823">"ඔබ (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"නම‍"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"යළි උත්සාහ කරන්න"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"මඟ හරින්න"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"තිර අගුලක් සකසන්න"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"ඔබගේ PIN තෝරන්න"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"ඔබේ රටාව තෝරා ගන්න"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"ඔබගේ මුරපදය තෝරන්න"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"වත්මන් තිර අගුල"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"ආරක්ෂාව සඳහා, රටාවක් සකසන්න"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"හිස් කරන්න"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"හිස් කරන්න"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"අවලංගු කරන්න"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"තහවුරු කරන්න"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"අවම වශයෙන් අනුලකුණු 4ක් විය යුතුය"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"අවම වශයෙන් 1 අංකයක් සමඟ මුරපදය අනුලකුණු 4-8 අතර විය යුතුය"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"අඩු තරමින් අකුරු <xliff:g id="COUNT">%d</xliff:g>ක් තිබිය යුතුය"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN හි අඩු තරමින් ඉලක්කම් <xliff:g id="COUNT">%d</xliff:g>ක් විය යුතුය"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"අනුලකුණු <xliff:g id="NUMBER">%d</xliff:g>කට වඩා අඩු විය යුතුය"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"උපාංග පරිපාලක මෑත PIN එකක් භාවිතා කිරීමට ඉඩ නොදේ"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ඔබේ IT පරිපාලක විසින් සුලබ PIN අවහිර කරනු ලැබේ. වෙනස් PIN එකක් උත්සාහ කරන්න."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"මෙහි අවලංගු අනුලකුණක් ඇතුළත් විය නොහැකිය."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"මුරපදය වලංගු නොවන අතර, අවම වශයෙන් අනුලකුණු 4ක් විය යුතුය."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"මුරපදය අවලංගුයි, අනුලකුණු 4-8, අවම වශයෙන් 1 ඉලක්කමක්, 1 අකුරක්, හිස් අවකාශයක් නොමැති විය යුතුය."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">අඩු තරමින් අකුරු <xliff:g id="COUNT">%d</xliff:g>ක් අඩංගු විය යුතුය</item>
       <item quantity="other">අඩු තරමින් අකුරු <xliff:g id="COUNT">%d</xliff:g>ක් අඩංගු විය යුතුය</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"පිහිටුවීම අවසන් කරන්න"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"දැන් නොවේ"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"විශේෂාංගය රිය පැදවීමේ දී නොමැත."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"රිය පැදවීමේ දී පරිශීලකයා එක් කළ නොහැක."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index b08e2eb..a323b4d 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Žiadosť o párovanie"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Zariadenie <xliff:g id="DEVICE_NAME">%1$s</xliff:g> spárujete klepnutím."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Jazyky"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Zvuk"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Hlasitosť zvonenia"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Hlasitosť navigácie"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Odstránenie používateľa"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nový používateľ"</string>
     <string name="user_guest" msgid="3465399481257448601">"Hosť"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Správca"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Prihlásený/-á ako správca"</string>
     <string name="user_switch" msgid="6544839750534690781">"Prepnúť"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Vy (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Meno"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Skúsiť znovu"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Preskočiť"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Nastavte zámku obrazovky"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Zvoľte si PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Vyberte vzor"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Zvoľte si heslo"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Aktuálna zámka obrazovky"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Z bezpečnostných dôvodov nastavte vzor"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Vymazať"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Vymazať"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Zrušiť"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Potvrdiť"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Musí obsahovať najmenej 4 znaky"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Heslo musí obsahovať 4 – 8 znakov a aspoň jedno číslo"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Minimálny počet znakov: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Minimálny počet čísel kódu PIN: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Musí mať menej ako <xliff:g id="NUMBER">%d</xliff:g> znakov"</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Správca zariadenia neumožňuje používať nedávny kód PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Bežné kódy PIN zablokoval správca IT. Skúste iný PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Musí obsahovať iba platné znaky."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Heslo nie je platné, musí obsahovať najmenej 4 znaky."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Heslo je neplatné, musí obsahovať 4 až 8 znakov a najmenej 1 číslicu, 1 písmeno a nesmie obsahovať medzeru."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="few">Musí obsahovať aspoň <xliff:g id="COUNT">%d</xliff:g> písmená</item>
       <item quantity="many">Must contain at least <xliff:g id="COUNT">%d</xliff:g> letters</item>
@@ -315,5 +310,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"dokončiť nastavenie"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"teraz nie"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funkcia nie je k dispozícii za jazdy."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Za jazdy nie je možné pridať používateľa."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 41726ac..7f59a32 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Zahteva za seznanitev"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Dotaknite se, če želite opraviti seznanitev z napravo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Jeziki"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Zvok"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Glasnost zvonjenja"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Glasnost navigacije"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Brisanje uporabnika"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Nov uporabnik"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gost"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Skrbnik"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Prijavljeni ste kot skrbnik"</string>
     <string name="user_switch" msgid="6544839750534690781">"Preklop"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Vi (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Ime"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Znova"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Preskoči"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Nastavite zaklepanje zaslona"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Izbira kode PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Izberite vzorec"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Izbira gesla"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Trenutno zaklepanje zaslona"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Nastavite vzorec za večjo varnost"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Počisti"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Počisti"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Prekliči"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Potrdi"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Geslo mora vsebovati najmanj 4 znake"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Geslo mora vsebovati od 4 do 8 znakov, od katerih mora biti najmanj 1 številka"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Geslo mora imeti najmanj toliko znakov: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Koda PIN mora biti vsaj <xliff:g id="COUNT">%d</xliff:g>-mestna"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Vsebovati mora manj kot toliko znakov: <xliff:g id="NUMBER">%d</xliff:g>."</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Skrbnik naprave ne dovoli uporabe nedavne kode PIN."</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Skrbnik za IT je blokiral pogoste kode PIN. Poskusite z drugo kodo PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Ne sme vsebovati neveljavnih znakov."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Geslo je neveljavno, vsebovati mora najmanj 4 znake."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Geslo je neveljavno. Vsebovati mora od 4 do 8 znakov, od tega najmanj 1 števko in 1 črko ter biti mora brez praznega prostora."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Vsebovati mora vsaj <xliff:g id="COUNT">%d</xliff:g> črko</item>
       <item quantity="two">Vsebovati mora vsaj <xliff:g id="COUNT">%d</xliff:g> črki</item>
@@ -315,5 +310,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"dokončaj nastavitev"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ne zdaj"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funkcija med vožnjo ni na voljo."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Med vožnjo ni mogoče dodati uporabnika."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 6f3214e..cb16e08 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Kërkesa e çiftimit"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Trokit për ta çiftuar me <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Gjuhët"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Tingulli"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volumi i ziles"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volumi i navigimit"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Fshi përdoruesin"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Përdorues i ri"</string>
     <string name="user_guest" msgid="3465399481257448601">"I ftuar"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administratori"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Identifikuar si administrator"</string>
     <string name="user_switch" msgid="6544839750534690781">"Ndërro"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Ti (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Emri"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Riprovo"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Kapërce"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Cakto një kyçje të ekranit"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Zgjidh kodin PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Zgjidh motivin"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Zgjidh fjalëkalimin"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Kyçja aktuale e ekranit"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Për siguri, cakto një motiv"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Pastro"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Pastro"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Anulo"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Konfirmo"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Duhet të jetë të paktën 4 karaktere"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Fjalëkalimi duhet të jetë 4-8 karaktere me të paktën 1 numër"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Duhet të jetë të paktën <xliff:g id="COUNT">%d</xliff:g> karaktere"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Kodi PIN duhet të jetë të paktën <xliff:g id="COUNT">%d</xliff:g> shifra"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Duhet të jetë më pak se <xliff:g id="NUMBER">%d</xliff:g> karaktere"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Administratori i pajisjes nuk e lejon përdorimin e një kodi PIN të përdorur së fundi"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Kodet e zakonshme PIN janë bllokuar nga administratori i TI-së. Provo një kod tjetër PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Kjo nuk mund të përfshijë një karakter të pavlefshëm."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Fjalëkalimi i pavlefshëm. Duhet të jetë të paktën 4 karaktere."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Fjalëkalimi është i pavlefshëm, duhet të jetë 4-8 karaktere, të përmbajë të paktën 1 shifër, 1 shkronjë, pa hapësira boshe."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Duhet të përmbajë të paktën <xliff:g id="COUNT">%d</xliff:g> shkronja</item>
       <item quantity="one">Duhet të përmbajë të paktën 1 shkronjë</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"përfundo konfigurimin"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"jo tani"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funksioni nuk ofrohet gjatë drejtimit të makinës."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Përdoruesi nuk mund të shtohet gjatë drejtimit të makinës"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 8f9f294..ac2fe0b 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Захтев за упаривање"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Додирните да бисте упарили са уређајем <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Језици"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Звук"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Јачина звука звона"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Јачина звука за навигацију"</string>
@@ -108,7 +107,7 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"Ниједна дозвола није одобрена"</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"Ниједна дозвола није захтевана"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"Потрошња података"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Потрошња података апл."</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"Потрошња података аплик."</string>
     <string name="force_stop" msgid="2153183697014720520">"Принудно заустави"</string>
     <string name="computing_size" msgid="5791407621793083965">"Израчунава се..."</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
@@ -171,8 +170,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Избришите корисника"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Нови корисник"</string>
     <string name="user_guest" msgid="3465399481257448601">"Гост"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Администратор"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Пријављени сте као администратор"</string>
     <string name="user_switch" msgid="6544839750534690781">"Пребаците на другог корисника"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Ви (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Име"</string>
@@ -215,9 +212,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Пробај поново"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Прескочи"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Подесите закључавање екрана"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Одаберите PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Одаберите шаблон"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Одаберите лозинку"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Тренутно закључавање екрана"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Подесите шаблон из безбедносних разлога"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Обриши"</string>
@@ -250,7 +245,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Обриши"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Откажи"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Потврди"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Мора да садржи најмање 4 знака"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Лозинка мора да има 4–8 знакова са најмање 1 бројем"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Минималан број знакова је <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Минималан број цифара за PIN је <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Максималан број знакова је <xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -259,7 +254,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Администратор уређаја не дозвољава употребу недавно коришћеног PIN-а"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ИТ администратор блокира честе PIN-ове. Изаберите други PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Не сме да обухвата неважећи знак."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Лозинка је неважећа, мора да садржи најмање 4 знака."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Лозинка је неважећа, мора да садржи 4–8 знакова, најмање 1 цифру, 1 слово, без размака."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Мора да садржи најмање <xliff:g id="COUNT">%d</xliff:g> слово</item>
       <item quantity="few">Мора да садржи најмање <xliff:g id="COUNT">%d</xliff:g> слова</item>
@@ -302,11 +297,12 @@
     <string name="backspace_key" msgid="1545590866688979099">"Тастер за брисање уназад"</string>
     <string name="enter_key" msgid="2121394305541579468">"Тастер Enter"</string>
     <string name="exit_retail_button_text" msgid="6093240315583384473">"Напустите режим демонстр."</string>
-    <string name="exit_retail_mode_dialog_title" msgid="7970631760237469168">"Да напустите режим демонстрације"</string>
+    <string name="exit_retail_mode_dialog_title" msgid="7970631760237469168">"Да напустите режим дем.?"</string>
     <string name="exit_retail_mode_dialog_body" msgid="8314316171782527301">"Овим ћете избрисати налог за демонстрације и ресетовати систем на фабричка подешавања. Сви подаци корисника ће бити изгубљени."</string>
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"Напусти режим демонстр."</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"завршите подешавање"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"не сада"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Функција није доступна током вожње."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Не можете да додате корисника током вожње."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 8cad62e..0e40d49 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Begäran om parkoppling"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Tryck om du vill parkoppla med <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Språk"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Ljud"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Ringvolym"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigeringsvolym"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Radera användare"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Ny användare"</string>
     <string name="user_guest" msgid="3465399481257448601">"Gäst"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administratör"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Inloggad som administratör"</string>
     <string name="user_switch" msgid="6544839750534690781">"Byt"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Du (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Namn"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Försök igen"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Hoppa över"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ange ett skärmlås"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Välj en pinkod"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Välj grafiskt lösenord"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Välj ett lösenord"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Nuvarande skärmlås"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Ange ett grafiskt lösenord som skydd"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Rensa"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Rensa"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Avbryt"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Bekräfta"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Måste innehålla minst fyra tecken"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Lösenordet måste bestå av 4–8 tecken med minst 1 siffra."</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Lösenordet måste innehålla minst <xliff:g id="COUNT">%d</xliff:g> tecken"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Pinkoden måste innehålla minst <xliff:g id="COUNT">%d</xliff:g> siffror"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Måste vara kortare än <xliff:g id="NUMBER">%d</xliff:g> tecken"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Enhetsadministratören tillåter inte att en pinkod som använts nyligen används igen"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"IT-adminstratören har blockerat de vanligaste pinkoderna. Testa en annan pinkod."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Får inte innehålla ogiltiga tecken."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Lösenordet är ogiltigt. Det måste innehålla minst fyra tecken."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Lösenordet är ogiltigt. Det måste bestå av 4–8 tecken, innehålla minst 1 siffra, 1 bokstav och inga blanksteg."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Lösenordet måste innehålla minst <xliff:g id="COUNT">%d</xliff:g> bokstäver</item>
       <item quantity="one">Lösenordet måste innehålla minst en bokstav</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"slutför konfigurationen"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"inte nu"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Funktionen är inte tillgänglig när du kör."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Du kan inte lägga till användare medan du kör."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 4efc4e6..b63c748 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Ombi la kuoanisha"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Gusa ili uoanishe na <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Lugha"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Mlio"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Sauti ya mlio"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Kiasi cha sauti"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Futa mtumiaji"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Mtumiaji mpya"</string>
     <string name="user_guest" msgid="3465399481257448601">"Mgeni"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Msimamizi"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Umeingia katika akaunti ya msimamizi"</string>
     <string name="user_switch" msgid="6544839750534690781">"Badilisha"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Wewe (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Jina"</string>
@@ -180,7 +177,7 @@
     <string name="users_list_title" msgid="770764290290240909">"Watumiaji"</string>
     <string name="accounts_settings_title" msgid="436190037084293471">"Akaunti"</string>
     <string name="user_details_title" msgid="1104762783367701498">"Mtumiaji"</string>
-    <string name="no_accounts_added" msgid="5148163140691096055">"Hakuna akaunti zilizoongezwa"</string>
+    <string name="no_accounts_added" msgid="5148163140691096055">"Hakuna akaunti zilizizoongezwa"</string>
     <string name="account_list_title" msgid="7631588514613843065">"Akaunti za <xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g>"</string>
     <string name="account_details_title" msgid="7529571432258448573">"Maelezo ya akaunti"</string>
     <string name="add_account_title" msgid="5988746086885210040">"Ongeza akaunti"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Jaribu tena"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Ruka"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Weka mbinu ya kufunga skrini"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Chagua PIN yako"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Chagua mchoro wako"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Chagua nenosiri lako"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Mbinu inayotumika kufunga skrini"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Kwa sababu za usalama, weka mchoro"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Futa"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Futa"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Ghairi"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Thibitisha"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Ni lazima liwe na angalau herufi 4"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Lazima nenosiri liwe na herufi 4-8 na angalau tarakimu 1"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Ni lazima liwe na angalau herufi <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Ni lazima PIN iwe na angalau tarakimu <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Lazima nenosiri liwe na chini ya herufi <xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Msimamizi wa kifaa haruhusu utumie PIN uliyotumia hivi majuzi"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"PIN zinazotumika zaidi zimezuiwa na msimamizi wako wa Tehama. Jaribu PIN tofauti."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Hali hii haiwezi kujumuisha herufi isiyoruhusiwa."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Nenosiri si sahihi. Ni lazima liwe na angalau herufi 4"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Nenosiri si sahihi, ni lazima liwe na kati ya herufi 4 hadi 8, liwe na tarakimu 1, herufi 1 ya alfabeti na lisiwe na nafasi."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Ni lazima liwe na angalau herufi <xliff:g id="COUNT">%d</xliff:g></item>
       <item quantity="one">Ni lazima liwe na angalau herufi 1</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"maliza kuweka mipangilio"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"si sasa"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Kipengele hakipatikani unapoendesha gari."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Huwezi kuongeza mtumiaji unapoendesha gari."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 0c81772..9b83b2a 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"இணைப்பிற்கான கோரிக்கை"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> உடன் இணைக்க, தட்டவும்."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"மொழிகள்"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ஒலி"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"ரிங் ஒலியளவு"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"வழிசெலுத்தலுக்கான ஒலியளவு"</string>
@@ -100,7 +99,7 @@
     <string name="media_volume_title" msgid="6697416686272606865">"மீடியா"</string>
     <string name="media_volume_summary" msgid="2961762827637127239">"இசை மற்றும் வீடியோக்களுக்கான ஒலியளவை அமை"</string>
     <string name="alarm_volume_title" msgid="840384014895796587">"அலாரம்"</string>
-    <string name="applications_settings" msgid="794261395191035632">"ஆப்ஸ் தகவல்"</string>
+    <string name="applications_settings" msgid="794261395191035632">"பயன்பாட்டுத் தகவல்"</string>
     <string name="disable_text" msgid="4358165448648990820">"முடக்கு"</string>
     <string name="enable_text" msgid="1794971777861881238">"இயக்கு"</string>
     <string name="permissions_label" msgid="2701446753515612685">"அனுமதிகள்"</string>
@@ -108,7 +107,7 @@
     <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"அனுமதிகள் எதுவும் வழங்கப்படவில்லை"</string>
     <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"அனுமதிகள் எதையும் கோரவில்லை"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"டேட்டா உபயோகம்"</string>
-    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"ஆப்ஸ் டேட்டா உபயோகம்"</string>
+    <string name="data_usage_app_summary_title" msgid="5012851696585421420">"பயன்பாட்டின் டேட்டா உபயோகம்"</string>
     <string name="force_stop" msgid="2153183697014720520">"உடனே நிறுத்து"</string>
     <string name="computing_size" msgid="5791407621793083965">"கணக்கிடுகிறது…"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"பயனரை நீக்கும்"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"புதிய பயனர்"</string>
     <string name="user_guest" msgid="3465399481257448601">"விருந்தினர்"</string>
-    <string name="user_admin" msgid="1535484812908584809">"நிர்வாகி"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"நிர்வாகியாக உள்நுழைந்துள்ளீர்கள்"</string>
     <string name="user_switch" msgid="6544839750534690781">"பயனரை மாற்றும்"</string>
     <string name="current_user_name" msgid="3813671533249316823">"நீங்கள் (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"பெயர்"</string>
@@ -191,7 +188,7 @@
     <string name="really_remove_account_message" msgid="4296769280849579900">"இந்தக் கணக்கை அகற்றினால், இதன் செய்திகள், தொடர்புகள், பிற தரவு ஆகியவை சாதனத்திலிருந்து நீக்கப்படும்!"</string>
     <string name="remove_account_failed" msgid="7472511529086294087">"உங்கள் நிர்வாகி இந்த மாற்றத்தை அனுமதிக்கவில்லை"</string>
     <string name="really_remove_user_title" msgid="4990029019291756762">"இந்தப் பயனரை அகற்றவா?"</string>
-    <string name="really_remove_user_message" msgid="3828090902833944533">"எல்லா ஆப்ஸும் டேட்டாவும் நீக்கப்படும்."</string>
+    <string name="really_remove_user_message" msgid="3828090902833944533">"எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string>
     <string name="remove_user_error_title" msgid="2038275458657689420">"பயனரை அகற்ற முடியவில்லை."</string>
     <string name="remove_user_error_message" msgid="6803947507134323358">"மீண்டும் முயலவா?"</string>
     <string name="remove_user_error_dismiss" msgid="4006591159426844335">"நிராகரி"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"மீண்டும் முயலவும்"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"தவிர்"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"திரைப் பூட்டை அமைக்கவும்"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"பின்னைத் தேர்ந்தெடுக்கவும்"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"பேட்டர்னைத் தேர்வுசெய்யவும்"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"கடவுச்சொல்லைத் தேர்ந்தெடுக்கவும்"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"தற்போதைய திரைப் பூட்டு"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"பாதுகாப்பிற்கு, பேட்டர்னை அமைக்கவும்"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"அழி"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"அழி"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"ரத்துசெய்"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"உறுதிப்படுத்து"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"குறைந்தது 4 எழுத்துகள் இருக்க வேண்டும்"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"கடவுச்சொல்லில் குறைந்தது 1 எண்ணுடன் சேர்த்து, 4-8 வரையிலான எழுத்துகள் இருக்க வேண்டும்"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"குறைந்தது <xliff:g id="COUNT">%d</xliff:g> எழுத்து இருக்க வேண்டும்"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"குறைந்தது <xliff:g id="COUNT">%d</xliff:g> இலக்கங்கள் இருக்க வேண்டும்"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g>எழுத்தை விட குறைவாக இருக்கவேண்டும்"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"சாதன நிர்வாகி சமீபத்திய பின்னைப் பயன்படுத்துவதை அனுமதிக்கவில்லை"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"உங்கள் IT நிர்வாகி, பொதுவான பின்களைத் தடுத்துள்ளார். வேறு பின்னைப் பயன்படுத்திப் பார்க்கவும்."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"இதில் தவறான எழுத்து இருக்கக்கூடாது."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"கடவுச்சொல் செல்லாது, குறைந்தது 4 எழுத்துகள் இருக்க வேண்டும்."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"கடவுச்சொல் தவறானது. இதில் 4-8 எழுத்துகள் இருக்க வேண்டும். குறைந்தது 1 இலக்கம், 1 எழுத்து இருக்க வேண்டும், இடைவெளி இருக்கக்கூடாது."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">குறைந்தது <xliff:g id="COUNT">%d</xliff:g> எழுத்துகள் இருக்க வேண்டும்</item>
       <item quantity="one">குறைந்தது 1 எழுத்து இருக்க வேண்டும்</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"அமைவை முடி"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"இப்போது வேண்டாம்"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"வாகனம் ஓட்டும்போது, இந்த அம்சத்தைப் பயன்படுத்த முடியாது."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"வாகனம் ஓட்டும்போது, பயனரைச் சேர்க்க முடியாது."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index dece14b..eb57843 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"జత చేయడానికి అభ్యర్థన"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>తో జత చేయడానికి నొక్కండి."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"భాషలు"</string>
     <string name="sound_settings" msgid="3072423952331872246">"ధ్వని"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"రింగ్ వాల్యూమ్"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"నావిగేషన్ వాల్యూమ్"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"వినియోగదారుని తొలగించు"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"కొత్త వినియోగదారు"</string>
     <string name="user_guest" msgid="3465399481257448601">"అతిథి"</string>
-    <string name="user_admin" msgid="1535484812908584809">"నిర్వాహకుడు"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"నిర్వాహకుడిగా సైన్ ఇన్ చేసారు"</string>
     <string name="user_switch" msgid="6544839750534690781">"మార్చు"</string>
     <string name="current_user_name" msgid="3813671533249316823">"మీరు (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"పేరు"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"మళ్లీ ప్రయత్నించు"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"దాటవేయి"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"స్క్రీన్ లాక్‌ను సెట్ చేయండి"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"మీ పిన్ ఎంచుకోండి"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"మీ నమూనాను ఎంచుకోండి"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"మీ పాస్‌వర్డ్‌ను ఎంచుకోండి"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"ప్రస్తుత స్క్రీన్ లాక్"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"భద్రత కోసం, ఆకృతిని సెట్ చేయండి"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"తీసివేయి"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"తీసివేయి"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"రద్దు చేయి"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"నిర్ధారించు"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"తప్పనిసరిగా కనీసం 4 అక్షరాలు ఉండాలి"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"పాస్‌వర్డ్ 4-8 అక్షరాల్లో ఉండాలి, అందులో కనీసం 1 సంఖ్య ఉండాలి"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"తప్పనిసరిగా కనీసం <xliff:g id="COUNT">%d</xliff:g> అక్షరాలు ఉండాలి"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"పిన్ తప్పనిసరిగా కనీసం <xliff:g id="COUNT">%d</xliff:g> అంకెలు ఉండాలి"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"తప్పనిసరిగా <xliff:g id="NUMBER">%d</xliff:g> కంటే తక్కువ అక్షరాలు ఉండాలి"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ఇటీవలి పిన్‌ని ఉపయోగించడానికి పరికర నిర్వాహకులు అనుమతించరు"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"సాధారణ పిన్‌లను మా IT నిర్వాహకులు బ్లాక్ చేసారు. వేరే పిన్‌ని ప్రయత్నించండి."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ఇందులో చెల్లని అక్షరం ఉండకూడదు."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"పాస్‌వర్డ్ చెల్లదు, తప్పనిసరిగా కనీసం 4 అక్షరాలు ఉండాలి."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"పాస్‌వర్డ్ చెల్లదు, తప్పనిసరిగా 4-8 అక్షరాల్లో ఉండాలి, వాటిలో కనీసం 1 అంకె, 1 వర్ణమాల అక్షరం ఉండాలి, తెల్లని ఖాళీ ఉండకూడదు."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">తప్పనిసరిగా కనీసం <xliff:g id="COUNT">%d</xliff:g> అక్షరాలను కలిగి ఉండాలి</item>
       <item quantity="one">తప్పనిసరిగా కనీసం 1 అక్షరాన్ని కలిగి ఉండాలి</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"డెమో నుండి నిష్క్రమించండి"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"సెటప్‌ను పూర్తి చేయండి"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ఇప్పుడు వద్దు"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"డ్రైవింగ్ చేస్తున్నప్పుడు ఫీచర్ అందుబాటులో ఉండదు."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"డ్రైవింగ్ చేస్తున్నప్పుడు వినియోగదారుని జోడించడం సాధ్యం కాదు."</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index a9d6540..a31612e 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"คำขอจับคู่อุปกรณ์"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"แตะเพื่อจับคู่กับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"ภาษา"</string>
     <string name="sound_settings" msgid="3072423952331872246">"เสียง"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"ระดับเสียงเรียกเข้า"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"ระดับเสียงของระบบนำทาง"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"ลบผู้ใช้"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"ผู้ใช้ใหม่"</string>
     <string name="user_guest" msgid="3465399481257448601">"ผู้มาเยือน"</string>
-    <string name="user_admin" msgid="1535484812908584809">"ผู้ดูแลระบบ"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"ลงชื่อเข้าใช้เป็นผู้ดูแลระบบ"</string>
     <string name="user_switch" msgid="6544839750534690781">"เปลี่ยน"</string>
     <string name="current_user_name" msgid="3813671533249316823">"คุณ (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"ชื่อ"</string>
@@ -214,15 +211,13 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"ลองอีกครั้ง"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"ข้าม"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"ตั้งค่าการล็อกหน้าจอ"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"เลือก PIN ของคุณ"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"เลือกรูปแบบของคุณ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"เลือกรหัสผ่านของคุณ"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"การล็อกหน้าจอปัจจุบัน"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"ตั้งรูปแบบเพื่อความปลอดภัย"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"ล้าง"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"ยกเลิก"</string>
     <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"รูปแบบการปลดล็อกใหม่ของคุณ"</string>
-    <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"ลากรูปแบบการปลดล็อก"</string>
+    <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"วาดรูปแบบการปลดล็อก"</string>
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"ปล่อยนิ้วเมื่อเสร็จแล้ว"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"บันทึกรูปแบบแล้ว"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"วาดรูปแบบอีกครั้งเพื่อยืนยัน"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"ล้าง"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"ยกเลิก"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"ยืนยัน"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"ต้องมีอักขระอย่างน้อย 4 ตัว"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"รหัสผ่านต้องมีอักขระระหว่าง 4-8 ตัว โดยจะต้องมีตัวเลขอย่างน้อย 1 ตัว"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"ต้องมีอักขระอย่างน้อย <xliff:g id="COUNT">%d</xliff:g> ตัว"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN ต้องมีตัวเลขอย่างน้อย <xliff:g id="COUNT">%d</xliff:g> ตัว"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"ต้องมีอักขระไม่เกิน <xliff:g id="NUMBER">%d</xliff:g> ตัว"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"ผู้ดูแลระบบอุปกรณ์ไม่อนุญาตให้ใช้ PIN ที่เพิ่งใช้ไป"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"ผู้ดูแลระบบไอทีบล็อก PIN ที่ไม่รัดกุม ลอง PIN อื่น"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"ต้องใช้อักขระที่ใช้ได้ทั้งหมด"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"รหัสผ่านไม่ถูกต้อง ต้องมีอักขระอย่างน้อย 4 ตัว"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"รหัสผ่านไม่ถูกต้อง จะต้องมีอักขระ 4-8 ตัว มีตัวเลขอย่างน้อย 1 ตัว ตัวอักษร 1 ตัว และห้ามเว้นวรรค"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">ต้องมีตัวอักษรอย่างน้อย <xliff:g id="COUNT">%d</xliff:g> ตัว</item>
       <item quantity="one">ต้องมีตัวอักษรอย่างน้อย 1 ตัว</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"ตั้งค่าให้เสร็จ"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ไว้ทีหลัง"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"ฟีเจอร์ไม่พร้อมใช้งานขณะขับรถ"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"เพิ่มผู้ใช้ขณะขับรถไม่ได้"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 59ae29e..bd02a67 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Kahilingan sa pagpapares"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"I-tap para makipagpares sa <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Mga Wika"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Tunog"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Volume ng pag-ring"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Volume ng navigation"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Mag-delete ng user"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Bagong user"</string>
     <string name="user_guest" msgid="3465399481257448601">"Bisita"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Admin"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Naka-sign in bilang admin"</string>
     <string name="user_switch" msgid="6544839750534690781">"Lumipat"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Ikaw (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Pangalan"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Subukang Muli"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Laktawan"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Magtakda ng lock ng screen"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Piliin ang iyong PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Pumili ng iyong pattern"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Piliin ang iyong password"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Kasalukuyang lock ng screen"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Para sa seguridad, magtakda ng pattern"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"I-clear"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"I-clear"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Kanselahin"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Kumpirmahin"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Dapat may kahit 4 na character lang"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Dapat ay may 4-8 character na may kahit 1 numero ang password"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Dapat ay may kahit <xliff:g id="COUNT">%d</xliff:g> (na) character"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Dapat ay may kahit <xliff:g id="COUNT">%d</xliff:g> (na) digit ang PIN"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Dapat ay wala pang <xliff:g id="NUMBER">%d</xliff:g> (na) character"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Hindi pinapayagan ng admin ng device ang paggamit ng kamakailang PIN"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Bina-block ng iyong IT admin ang mga pangkaraniwang PIN. Sumubok ng ibang PIN."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Hindi ito maaaring maglaman ng invalid na character."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Invalid ang password, dapat may kahit 4 na character lang."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Invalid ang password. Binubuo dapat ito ng 4-8 character, may kahit 1 digit, 1 titik, at walang whitespace."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Dapat ay may kahit <xliff:g id="COUNT">%d</xliff:g> titik</item>
       <item quantity="other">Dapat ay may kahit <xliff:g id="COUNT">%d</xliff:g> na titik</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"tapusin ang pag-set up"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"huwag ngayon"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Hindi available ang feature habang nagmamaneho."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Hindi maaaring magdagdag ng user habang nagmamaneho."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 57014c1..060d09f 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Eşleme isteği"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlemek için dokunun."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Diller"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Ses"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Zil sesi düzeyi"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Gezinme ses düzeyi"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Kullanıcıyı sil"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Yeni kullanıcı"</string>
     <string name="user_guest" msgid="3465399481257448601">"Misafir"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Yönetici"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Yönetici olarak oturum açıldı"</string>
     <string name="user_switch" msgid="6544839750534690781">"Değiştir"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Siz (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Ad"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Tekrar dene"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Atla"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ekran kilidi ayarlama"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PIN\'inizi seçin"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Deseninizi seçin"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Şifrenizi seçin"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Geçerli ekran kilidi"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Güvenlik için desen oluşturun"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Temizle"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Temizle"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"İptal et"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Onayla"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"En az 4 karakterli olmalıdır"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"En az 1 sayı içeren 4-8 karakter girin"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"En az <xliff:g id="COUNT">%d</xliff:g> karakter olmalıdır"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN en az <xliff:g id="COUNT">%d</xliff:g> basamaklı olmalıdır"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"<xliff:g id="NUMBER">%d</xliff:g> karakterden kısa olmalıdır"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Cihaz yöneticisi yakın zamanda kullanılan bir PIN\'e izin vermiyor"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Yaygın olarak kullanılan PIN\'ler BT yöneticiniz tarafından engellenir. Farklı bir PIN deneyin."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Geçersiz karakter kullanılamaz."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Şifre geçersiz, en az 4 karakterli olmalıdır"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Şifre geçersiz; 4-8 karakterden oluşmalı, en az 1 rakam ve 1 harf içermeli, boşluksuz olmalıdır."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">En az <xliff:g id="COUNT">%d</xliff:g> harf içermelidir</item>
       <item quantity="one">En az 1 harf içermelidir</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"kurulumu bitir"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"şimdi değil"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Sürüş sırasında bu özellik kullanılamaz."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Sürüş sırasında kullanıcı eklenemez."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 9c6e6ee..c629801 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Запит на створення пари"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Торкніться, щоб підключитися до пристрою <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Мови"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Звук"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Гучність дзвінка"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Гучність навігації"</string>
@@ -137,7 +136,7 @@
     <string name="legal_information" msgid="1838443759229784762">"Правова інформація"</string>
     <string name="contributors_title" msgid="7698463793409916113">"Співавтори"</string>
     <string name="manual" msgid="4819839169843240804">"Посібник"</string>
-    <string name="regulatory_labels" msgid="3165587388499646779">"Сертифікації"</string>
+    <string name="regulatory_labels" msgid="3165587388499646779">"Нормативні мітки"</string>
     <string name="safety_and_regulatory_info" msgid="1204127697132067734">"Посібник із безпеки та нормативних вимог"</string>
     <string name="copyright_title" msgid="4220237202917417876">"Авторські права"</string>
     <string name="license_title" msgid="936705938435249965">"Ліцензія"</string>
@@ -156,7 +155,7 @@
     <string name="zone_auto" msgid="3701878581920206160">"Автоматичний часовий пояс"</string>
     <string name="zone_auto_summary" msgid="4345856882906981864">"Використовувати часовий пояс, наданий мережею"</string>
     <string name="date_time_24hour_title" msgid="3025576547136168692">"24-годинний формат"</string>
-    <string name="date_time_24hour" msgid="1137618702556486913">"24-годинний формат"</string>
+    <string name="date_time_24hour" msgid="1137618702556486913">"Використовувати 24-годинний формат"</string>
     <string name="date_time_set_time_title" msgid="5884883050656937853">"Час"</string>
     <string name="date_time_set_time" msgid="6449555153906058248">"Установити час"</string>
     <string name="date_time_set_timezone_title" msgid="3001779256157093425">"Часовий пояс"</string>
@@ -172,8 +171,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Як видалити користувача"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Новий користувач"</string>
     <string name="user_guest" msgid="3465399481257448601">"Гість"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Адміністратор"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Ви ввійшли як адміністратор"</string>
     <string name="user_switch" msgid="6544839750534690781">"Змінити користувача"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Ви (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Ім’я"</string>
@@ -216,9 +213,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Повторити"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Пропустити"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Налаштуйте блокування екрана"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Придумайте PIN-код"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Створіть ключ"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Придумайте пароль"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Поточне блокування екрана"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"З міркувань безпеки налаштуйте ключ"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Очистити"</string>
@@ -251,7 +246,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Очистити"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Скасувати"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Підтвердити"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Потрібно ввести принаймні 4 символи"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Пароль має складатися з 4–8 символів і містити принаймні 1 цифру"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Мінімальна кількість символів: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Мінімальна кількість цифр у PIN-коді: <xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Максимальна кількість символів: <xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -260,7 +255,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Адміністратор пристрою не дозволяє використовувати останній PIN-код"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"IT-адміністратор заблокував загальні PIN-коди. Введіть інший PIN-код."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Не може містити недійсні символи."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Пароль недійсний. Потрібно ввести принаймні 4 символи."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Пароль недійсний. Він має складатися з 4–8 символів і містити принаймні 1 цифру й 1 літеру без пробілів."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Має містити принаймні <xliff:g id="COUNT">%d</xliff:g> літеру</item>
       <item quantity="few">Має містити принаймні <xliff:g id="COUNT">%d</xliff:g> літери</item>
@@ -315,5 +310,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"завершити налаштування"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"не зараз"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Функція недоступна під час руху автомобіля."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Неможливо додати користувача під час руху автомобіля."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 26832bd..021eb38 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"جوڑا بنانے کی درخواست"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا بنانے کیلئے تھپتھپائیں۔"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"زبانیں"</string>
     <string name="sound_settings" msgid="3072423952331872246">"آواز"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"رِنگ والیوم"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"نیویگیشن والیوم"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"صارف کو حذف کریں"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"نیا صارف"</string>
     <string name="user_guest" msgid="3465399481257448601">"مہمان"</string>
-    <string name="user_admin" msgid="1535484812908584809">"منتظم"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"منتظم کے طور پر سائن ان کردہ"</string>
     <string name="user_switch" msgid="6544839750534690781">"سوئچ کریں"</string>
     <string name="current_user_name" msgid="3813671533249316823">"آپ (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"نام"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"دوبارہ کوشش کریں"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"نظر انداز کریں"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"اسکرین لاک سیٹ کریں"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"اپنا PIN منتخب کریں"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"اپنا پیٹرن منتخب کریں"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"اپنا پاس ورڈ منتخب کریں"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"موجودہ اسکرین لاک"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"سیکیورٹی کیلئے پیٹرن سیٹ کریں"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"صاف کریں"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"صاف کریں"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"منسوخ کریں"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"تصدیق کریں"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"کم از کم 4 حروف ہونا ضروری ہے"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"پاس ورڈ 4 سے 8 کیریکٹرز کا ہونا چاہئے جس میں کم از کم 1 نمبر ہونا چاہئے"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"کم از کم <xliff:g id="COUNT">%d</xliff:g> حروف پر مشتمل ہونا چاہیے"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN کم از کم <xliff:g id="COUNT">%d</xliff:g> ہندسوں کا ہونا چاہیے"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"کریکٹرز <xliff:g id="NUMBER">%d</xliff:g> سے کم ہونے چاہئیں"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"آلہ کا منتظم ایک حالیہ PIN استعمال کرنے کی اجازت نہیں دیتا ہے"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"آپ کے IT منتظم نے عمومی PINs کو مسدود کر دیا ہے۔ کوئی دوسرا PIN آزمائیں۔"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"اس میں غلط کریکٹر شامل نہیں ہو سکتا۔"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"پاس ورڈ غلط ہے، کم از کم 4 حروف لمبا ہونا ضروری ہے۔"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"پاس ورڈ غلط ہے، 4 سے 8 کیریکٹرز کا ہونا لازمی ہے، کم از کم 1 ہندسہ، 1 حرف ہونا چاہئے، کوئی وہائٹ اسپیس نہیں ہونا چاہئے۔"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">کم از کم <xliff:g id="COUNT">%d</xliff:g> حروف پر مشتمل ہونا چاہیے</item>
       <item quantity="one">کم از کم 1 حرف پر مشتمل ہونا چاہیئے</item>
@@ -300,6 +295,8 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"ڈیمو سے باہر نکلیں"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"سیٹ اپ مکمل کریں"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"ابھی نہیں"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"ڈرائیونگ کے دوران یہ خصوصیت دستیاب نہیں ہے۔"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"ڈرائیونگ کے دوران صارف کو شامل نہیں کیا جا سکتا۔"</string>
+    <!-- no translation found for restricted_while_driving (6217369093121968299) -->
+    <skip />
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index e4402aa..f7295ae 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Juftlash talabi"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasiga ulanish uchun bosing."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Tillar"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Ovoz"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Chaqiruv tovushi"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Navigatsiya tovushi"</string>
@@ -142,7 +141,7 @@
     <string name="terms_title" msgid="5201471373602628765">"Foydalanish shartlari"</string>
     <string name="webview_license_title" msgid="2531829466541104826">"WebView tizim litsenziyasi"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"Orqa fon rasmlari"</string>
-    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Sputnik tasvirlari:\n© CNES 2014/Astrium, DigitalGlobe, Bluesky"</string>
+    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"Sun’iy yo‘ldosh tasvirlari:\n© CNES 2014/Astrium, DigitalGlobe, Bluesky"</string>
     <string name="settings_license_activity_title" msgid="8499293744313077709">"Uchinchi tomon litsenziyalari"</string>
     <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"Litsenziyalarni yuklashda muammo yuz berdi."</string>
     <string name="settings_license_activity_loading" msgid="6163263123009681841">"Yuklanmoqda…"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Foydalanuvchini o‘chirib tashlash"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Yangi foydalanuvchi"</string>
     <string name="user_guest" msgid="3465399481257448601">"Mehmon"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Administrator"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Administrator sifatida kirgansiz"</string>
     <string name="user_switch" msgid="6544839750534690781">"Almashtirish"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Siz (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Ism"</string>
@@ -180,11 +177,11 @@
     <string name="users_list_title" msgid="770764290290240909">"Foydalanuvchilar"</string>
     <string name="accounts_settings_title" msgid="436190037084293471">"Hisoblar"</string>
     <string name="user_details_title" msgid="1104762783367701498">"Foydalanuvchi"</string>
-    <string name="no_accounts_added" msgid="5148163140691096055">"Hech qanday hisobga kirilmagan"</string>
+    <string name="no_accounts_added" msgid="5148163140691096055">"Hech qanday hisob qo‘shilmagan"</string>
     <string name="account_list_title" msgid="7631588514613843065">"<xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g> – hisoblar"</string>
     <string name="account_details_title" msgid="7529571432258448573">"Hisob haqida"</string>
     <string name="add_account_title" msgid="5988746086885210040">"Hisob qo‘shish"</string>
-    <string name="add_an_account" msgid="1072285034300995091">"Yangi hisob kiritish"</string>
+    <string name="add_an_account" msgid="1072285034300995091">"Yangi hisob qo‘shish"</string>
     <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"Cheklangan profillar hisoblar qo‘sha olmaydilar"</string>
     <string name="remove_account_title" msgid="8840386525787836381">"Hisobni olib tashlash"</string>
     <string name="really_remove_account_title" msgid="3555164432587924900">"Hisob olib tashlansinmi?"</string>
@@ -214,16 +211,14 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Qayta urinish"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Tashlab ketish"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Ekran qulfini sozlang"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"PIN kod tanlang"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Grafik kalit yarating"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Parol tanlang"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Joriy ekran qulfi"</string>
-    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Xavfsizlik uchun grafik kalit yarating"</string>
+    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Xavfsizlik uchun grafik kalit o‘rnating"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Tozalash"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"Bekor qilish"</string>
     <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"Yangi grafik kalitingiz"</string>
     <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"Grafik kalitni chizing"</string>
-    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Tugagach, barmoqni ekrandan oling"</string>
+    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Tayyor bo‘lgach, barmog‘ingizni oling"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"Grafik kalit saqlandi"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"Kalitni yana bir marta chizing"</string>
     <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Kamida 4 ta nuqtani ulang. Qaytadan urining."</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Tozalash"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Bekor qilish"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Tasdiqlash"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Kamida 4 ta belgi kiriting"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Parol 4-8 ta belgidan, kamida 1 ta raqamli bo‘lsin"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Kamida <xliff:g id="COUNT">%d</xliff:g> ta belgi bo‘lishi kerak"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN kod kamida <xliff:g id="COUNT">%d</xliff:g> raqamli bo‘lsin"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Parol maksimum <xliff:g id="NUMBER">%d</xliff:g> ta belgili bo‘lsin"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Qurilma administratori oxirgi PIN koddan yana foydalanishga ruxsat bermaydi"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Oddiy PIN kodlar AT admini tomonidan bloklangan. Murakkabroq PIN kod tanlang."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Parol yaroqsiz belgidan iborat bo‘lmasligi lozim."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Parol yaroqsiz, kamida 4 ta belgi kiriting"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Parol xato, 4-8 ta belgidan, kamida 1 ta raqam va 1 ta harf hamda bo‘sh joysiz bo‘lishi lozim."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Parol tarkibida kamida <xliff:g id="COUNT">%d</xliff:g> ta harf bo‘lishi lozim</item>
       <item quantity="one">Parol tarkibida kamida 1 ta harf bo‘lishi lozim</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"sozlashni tugatish"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"hozir emas"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Avtomobilda harakatlanayotganda bu funksiya ishlamaydi."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Avtomobilda harakatlanayotganda foydalanuvchi qo‘shilmaydi."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index fa673f9..a4c6aa1 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Yêu cầu ghép nối"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Nhấn để ghép nối với <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Ngôn ngữ"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Âm báo"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Âm lượng chuông"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Âm lượng điều hướng"</string>
@@ -119,7 +118,7 @@
     <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"Bản cập nhật hệ thống"</string>
     <string name="system_update_settings_list_item_summary" msgid="7395202602021608371"></string>
     <string name="firmware_version" msgid="8491753744549309333">"Phiên bản Android"</string>
-    <string name="security_patch" msgid="4794276590178386903">"Cấp bản vá bảo mật của Android"</string>
+    <string name="security_patch" msgid="4794276590178386903">"Mức bản vá bảo mật của Android"</string>
     <string name="model_info" msgid="4966408071657934452">"Kiểu máy"</string>
     <string name="baseband_version" msgid="2370088062235041897">"Phiên bản băng tần cơ sở"</string>
     <string name="kernel_version" msgid="7327212934187011508">"Phiên bản Kernel"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Xóa người dùng"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Người dùng mới"</string>
     <string name="user_guest" msgid="3465399481257448601">"Khách"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Quản trị viên"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Đã đăng nhập là quản trị viên"</string>
     <string name="user_switch" msgid="6544839750534690781">"Nút chuyển"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Bạn (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Tên"</string>
@@ -197,46 +194,44 @@
     <string name="remove_user_error_dismiss" msgid="4006591159426844335">"Bỏ qua"</string>
     <string name="remove_user_error_retry" msgid="8291692909396995093">"Thử lại"</string>
     <string name="user_add_user_title" msgid="7458813670614932479">"Thêm người dùng mới?"</string>
-    <string name="user_add_user_message_setup" msgid="6030901156040053106">"Khi bạn thêm một người dùng mới, họ cần thiết lập không gian của mình."</string>
+    <string name="user_add_user_message_setup" msgid="6030901156040053106">"Khi bạn thêm một người dùng mới, người đó cần thiết lập không gian của họ."</string>
     <string name="user_add_user_message_update" msgid="1528170913388932459">"Bất kỳ người dùng nào cũng có thể cập nhật ứng dụng cho tất cả những người dùng khác."</string>
     <string name="security_settings_title" msgid="6955331714774709746">"Bảo mật"</string>
-    <string name="security_settings_subtitle" msgid="2244635550239273229">"Phương thức khóa màn hình"</string>
+    <string name="security_settings_subtitle" msgid="2244635550239273229">"Khóa màn hình"</string>
     <string name="security_lock_none" msgid="1054645093754839638">"Không"</string>
     <string name="security_lock_pattern" msgid="1174352995619563104">"Hình mở khóa"</string>
     <string name="security_lock_pin" msgid="4891899974369503200">"Mã PIN"</string>
     <string name="security_lock_password" msgid="4420203740048322494">"Mật khẩu"</string>
     <string name="lock_settings_picker_title" msgid="6590330165050361632">"Chọn một cách khóa"</string>
-    <string name="screen_lock_options" msgid="7023338635352915768">"Tùy chọn phương thức khóa màn hình"</string>
+    <string name="screen_lock_options" msgid="7023338635352915768">"Tùy chọn khóa màn hình"</string>
     <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"Nhập hình mở khóa của bạn"</string>
     <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"Xác nhận"</string>
     <string name="lockpattern_restart_button_text" msgid="9355771277617537">"Vẽ lại"</string>
     <string name="continue_button_text" msgid="5129979170426836641">"Tiếp tục"</string>
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Thử lại"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Bỏ qua"</string>
-    <string name="set_screen_lock" msgid="5239317292691332780">"Đặt phương thức khóa màn hình"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Chọn mã PIN của bạn"</string>
+    <string name="set_screen_lock" msgid="5239317292691332780">"Đặt khóa màn hình"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Chọn hình mở khóa của bạn"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Chọn mật khẩu của bạn"</string>
-    <string name="current_screen_lock" msgid="637651611145979587">"Phương thức khóa màn hình hiện tại"</string>
+    <string name="current_screen_lock" msgid="637651611145979587">"Khóa màn hình hiện tại"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Để bảo mật, hãy đặt hình mở khóa"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Xóa"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"Hủy"</string>
-    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"Hình mở khóa mới"</string>
+    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"Hình mở khóa mới của bạn"</string>
     <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"Vẽ hình mở khóa"</string>
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"Thả ngón tay khi hoàn tất"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"Đã ghi lại hình mở khóa"</string>
     <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"Vẽ lại hình mở khóa để xác nhận"</string>
     <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"Kết nối ít nhất 4 điểm. Hãy thử lại."</string>
-    <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"Hình không chính xác"</string>
+    <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"Hình mở khóa sai"</string>
     <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"Cách vẽ hình mở khóa"</string>
     <string name="error_saving_lockpattern" msgid="2933512812768570130">"Lỗi khi lưu mẫu"</string>
     <string name="okay" msgid="4589873324439764349">"OK"</string>
-    <string name="remove_screen_lock_title" msgid="1234382338764193387">"Xóa phương thức khóa màn hình?"</string>
+    <string name="remove_screen_lock_title" msgid="1234382338764193387">"Xóa khóa màn hình?"</string>
     <string name="remove_screen_lock_message" msgid="6675850371585564965">"Thao tác này sẽ cho phép bất cứ ai truy cập vào tài khoản của bạn"</string>
     <string name="lock_settings_enter_pin" msgid="1669172111244633904">"Nhập mã PIN của bạn"</string>
     <string name="lock_settings_enter_password" msgid="2636669926649496367">"Nhập mật khẩu của bạn"</string>
     <string name="choose_lock_pin_message" msgid="2963792070267774417">"Để bảo mật, hãy đặt mã PIN"</string>
-    <string name="confirm_your_pin_header" msgid="9096581288537156102">"Nhập lại mã PIN"</string>
+    <string name="confirm_your_pin_header" msgid="9096581288537156102">"Nhập lại mã PIN của bạn"</string>
     <string name="choose_lock_pin_hints" msgid="7362906249992020844">"PIN phải có ít nhất 4 chữ số"</string>
     <string name="lockpin_invalid_pin" msgid="2149191577096327424">"Mã PIN không hợp lệ. Mã này phải có ít nhất 4 chữ số."</string>
     <string name="confirm_pins_dont_match" msgid="4607110139373520720">"Các mã PIN không khớp"</string>
@@ -244,12 +239,12 @@
     <string name="lockscreen_wrong_pin" msgid="4922465731473805306">"Mã PIN sai"</string>
     <string name="lockscreen_wrong_password" msgid="5757087577162231825">"Mật khẩu sai"</string>
     <string name="choose_lock_password_message" msgid="6124341145027370784">"Để bảo mật, hãy đặt mật khẩu"</string>
-    <string name="confirm_your_password_header" msgid="7052891840366724938">"Nhập lại mật khẩu"</string>
+    <string name="confirm_your_password_header" msgid="7052891840366724938">"Nhập lại mật khẩu của bạn"</string>
     <string name="confirm_passwords_dont_match" msgid="7300229965206501753">"Mật khẩu không khớp"</string>
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Xóa"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Hủy"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Xác nhận"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Mật khẩu phải chứa ít nhất 4 ký tự"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Mật khẩu phải có 4-8 ký tự, ít nhất 1 số"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Phải chứa ít nhất <xliff:g id="COUNT">%d</xliff:g> ký tự"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Mã PIN phải chứa ít nhất <xliff:g id="COUNT">%d</xliff:g> chữ số"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Chỉ được có ít hơn <xliff:g id="NUMBER">%d</xliff:g> ký tự"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Quản trị viên thiết bị không cho phép dùng mã PIN gần đây"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Quản vị viên CNTT đã chặn những mã PIN phổ biến. Hãy thử mã PIN khác."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Mật khẩu này không được bao gồm ký tự không hợp lệ."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Mật khẩu không hợp lệ, phải có ít nhất 4 ký tự."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Mật khẩu không hợp lệ. Mật khẩu phải dài từ 4 đến 8 ký tự, chứa ít nhất 1 chữ số, 1 chữ cái, không có khoảng trắng."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">Phải chứa ít nhất <xliff:g id="COUNT">%d</xliff:g> chữ cái</item>
       <item quantity="one">Phải chứa ít nhất 1 chữ cái</item>
@@ -287,7 +282,7 @@
     <string name="error_saving_password" msgid="8334882262622500658">"Lỗi khi lưu mật khẩu"</string>
     <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"Quản trị viên CNTT đã chặn những mật khẩu phổ biến. Hãy thử mật khẩu khác."</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="38813552228809240">"Không cho phép thứ tự chữ số tăng dần, giảm dần hoặc lặp lại."</string>
-    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"Tùy chọn phương thức khóa màn hình"</string>
+    <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"Tùy chọn khóa màn hình"</string>
     <string name="forget" msgid="3971143908183848527">"Xóa"</string>
     <string name="delete_button" msgid="5840500432614610850">"Xóa"</string>
     <string name="remove_button" msgid="6664656962868194178">"Xóa"</string>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"hoàn tất thiết lập"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"để sau"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Bạn không sử dụng được tính năng này khi đang lái xe."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Không thể thêm người dùng trong khi lái xe."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index a204017..9ce6f56 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"配对请求"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"点按即可与 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配对。"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"语言"</string>
     <string name="sound_settings" msgid="3072423952331872246">"提示音"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"铃声音量"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"导航音量"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"删除用户"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"新用户"</string>
     <string name="user_guest" msgid="3465399481257448601">"访客"</string>
-    <string name="user_admin" msgid="1535484812908584809">"管理员"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"目前登录的是管理员帐号"</string>
     <string name="user_switch" msgid="6544839750534690781">"开关"</string>
     <string name="current_user_name" msgid="3813671533249316823">"您 (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"名称"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"重试"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"跳过"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"设置屏幕锁定"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"选择 PIN 码"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"选择您的图案"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"选择密码"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"当前屏幕锁定设置"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"为了安全起见,请设置解锁图案"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"清除"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"清除"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"取消"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"确认"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"必须至少包含 4 个字符"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"密码必须介于 4-8 个字符之间,并且至少包含 1 个数字"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"必须至少包含 <xliff:g id="COUNT">%d</xliff:g> 个字符"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN 码必须至少为 <xliff:g id="COUNT">%d</xliff:g> 位数"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"必须少于 <xliff:g id="NUMBER">%d</xliff:g> 个字符"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"设备管理员不允许使用最近用过的 PIN 码"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"常用 PIN 码已被您的 IT 管理员屏蔽。请尝试一个不同的 PIN 码。"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"此密码不得包含无效字符。"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"密码无效,密码必须至少包含 4 个字符。"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"密码无效。密码的长度必须介于 4-8 个字符之间,并包含至少 1 个数字和 1 个字母,且不得包含空格。"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">必须包含至少 <xliff:g id="COUNT">%d</xliff:g> 个字母</item>
       <item quantity="one">必须包含至少 1 个字母</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"完成设置"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"以后再说"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"无法在驾驶时使用此功能。"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"驾车时无法添加用户。"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index aafae7a..8e8e55a 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -20,28 +20,28 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="settings_label" msgid="5147911978211079839">"設定"</string>
     <string name="more_settings_label" msgid="3867559443480110616">"更多"</string>
-    <string name="display_settings" msgid="5325515247739279185">"顯示"</string>
+    <string name="display_settings" msgid="5325515247739279185">"螢幕"</string>
     <string name="brightness" msgid="2919605130898772866">"亮度"</string>
-    <string name="auto_brightness_title" msgid="9124647862844666581">"自動調校光暗"</string>
-    <string name="auto_brightness_summary" msgid="4741887033140384352">"按環境光線優化亮度"</string>
-    <string name="condition_night_display_title" msgid="3777509730126972675">"已開啟「夜燈模式」"</string>
+    <string name="auto_brightness_title" msgid="9124647862844666581">"自動調整亮度"</string>
+    <string name="auto_brightness_summary" msgid="4741887033140384352">"根據環境光源調整亮度"</string>
+    <string name="condition_night_display_title" msgid="3777509730126972675">"夜燈模式已開啟"</string>
     <string name="keywords_display" msgid="3978416985146943922">"螢幕, 觸控螢幕"</string>
-    <string name="keywords_display_brightness_level" msgid="3956411572536209195">"螢幕變暗, 觸控螢幕, 電池"</string>
-    <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"螢幕變暗, 觸控螢幕, 電池"</string>
-    <string name="keywords_display_night_display" msgid="2922294576679769957">"螢幕變暗, 夜間, 轉色"</string>
+    <string name="keywords_display_brightness_level" msgid="3956411572536209195">"調暗螢幕, 觸控螢幕, 電池"</string>
+    <string name="keywords_display_auto_brightness" msgid="2700310050333468752">"調暗螢幕, 觸控螢幕, 電池"</string>
+    <string name="keywords_display_night_display" msgid="2922294576679769957">"調暗螢幕, 夜間, 色調"</string>
     <string name="night_mode_tile_label" msgid="6603597795502131664">"夜間模式"</string>
-    <string name="wifi_settings" msgid="7701477685273103841">"Wi-Fi"</string>
-    <string name="wifi_settings_summary" msgid="6095898149997291025">"設定和管理無線網絡接入點"</string>
+    <string name="wifi_settings" msgid="7701477685273103841">"Wi‑Fi"</string>
+    <string name="wifi_settings_summary" msgid="6095898149997291025">"設定、管理無線網路存取點"</string>
     <string name="wifi_starting" msgid="473253087503153167">"正在開啟 Wi-Fi…"</string>
     <string name="wifi_stopping" msgid="3534173972547890148">"正在關閉 Wi-Fi…"</string>
-    <string name="wifi_failed_forget_message" msgid="121732682699377206">"無法刪除網絡"</string>
-    <string name="wifi_failed_connect_message" msgid="4447498225022147324">"無法連接網絡"</string>
-    <string name="wifi_setup_add_network" msgid="3660498520389954620">"新增網絡"</string>
+    <string name="wifi_failed_forget_message" msgid="121732682699377206">"無法清除網路設定"</string>
+    <string name="wifi_failed_connect_message" msgid="4447498225022147324">"無法連上網路"</string>
+    <string name="wifi_setup_add_network" msgid="3660498520389954620">"新增網路"</string>
     <string name="wifi_disabled" msgid="5013262438128749950">"Wi-Fi 已停用"</string>
     <string name="wifi_setup_connect" msgid="3512399573397979101">"連線"</string>
     <string name="wifi_password" msgid="5565632142720292397">"密碼"</string>
     <string name="wifi_show_password" msgid="8423293211933521097">"顯示密碼"</string>
-    <string name="wifi_ssid" msgid="488604828159458741">"網絡名稱"</string>
+    <string name="wifi_ssid" msgid="488604828159458741">"網路名稱"</string>
     <string name="wifi_security" msgid="158358046038876532">"安全性"</string>
     <string name="wifi_signal" msgid="1817579728350364549">"訊號強度"</string>
     <string name="wifi_status" msgid="5688013206066543952">"狀態"</string>
@@ -50,48 +50,47 @@
     <string name="wifi_ip_address" msgid="3128140627890954061">"IP 位址"</string>
     <string name="access_point_tag_key" msgid="1517143378973053337">"access_point_tag_key"</string>
   <string-array name="wifi_signals">
-    <item msgid="4897376984576812606">"弱"</item>
-    <item msgid="2032262610626057081">"尚可"</item>
-    <item msgid="3859756017461098953">"強"</item>
-    <item msgid="1521103743353335724">"非常強"</item>
+    <item msgid="4897376984576812606">"差"</item>
+    <item msgid="2032262610626057081">"一般"</item>
+    <item msgid="3859756017461098953">"良好"</item>
+    <item msgid="1521103743353335724">"極佳"</item>
   </string-array>
     <string name="bluetooth_quick_toggle_title" msgid="637869245038061523">"藍牙"</string>
     <string name="bluetooth_quick_toggle_summary" msgid="4390699431893305353">"開啟藍牙"</string>
     <string name="bluetooth_settings" msgid="3878243366013638982">"藍牙"</string>
     <string name="bluetooth_disabled" msgid="4187409401590350572">"藍牙已停用"</string>
     <string name="bluetooth_settings_title" msgid="3794688574569688649">"藍牙"</string>
-    <string name="bluetooth_settings_summary" msgid="4023303473646769835">"管理連線、設定裝置名稱和可偵測度"</string>
+    <string name="bluetooth_settings_summary" msgid="4023303473646769835">"管理連線,設定裝置名稱和可偵測性"</string>
     <string name="bluetooth_talkback_computer" msgid="5223330129934365312">"電腦"</string>
     <string name="bluetooth_talkback_headset" msgid="6155254514321149935">"耳機"</string>
     <string name="bluetooth_talkback_phone" msgid="8833977851215000426">"手機"</string>
-    <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"映像裝置"</string>
+    <string name="bluetooth_talkback_imaging" msgid="8762390801115154654">"顯像裝置"</string>
     <string name="bluetooth_talkback_headphone" msgid="5362155791551671490">"耳機"</string>
-    <string name="bluetooth_talkback_input_peripheral" msgid="868933277567862622">"輸入周邊裝置"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="868933277567862622">"周邊輸入裝置"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1715933297419387985">"藍牙"</string>
-    <string name="bluetooth_preference_paired_devices" msgid="5875643105380630583">"已配對的裝置"</string>
+    <string name="bluetooth_preference_paired_devices" msgid="5875643105380630583">"配對裝置"</string>
     <string name="bluetooth_preference_found_devices" msgid="125155123214560511">"可用的裝置"</string>
-    <string name="bluetooth_preference_no_paired_devices" msgid="483742146117390001">"找不到已配對的裝置"</string>
+    <string name="bluetooth_preference_no_paired_devices" msgid="483742146117390001">"沒有任何配對裝置"</string>
     <string name="bluetooth_preference_no_found_devices" msgid="1391812056491062262">"沒有可用的裝置"</string>
-    <string name="bluetooth_preference_paired_dialog_title" msgid="2470829827455850904">"已配對的裝置"</string>
+    <string name="bluetooth_preference_paired_dialog_title" msgid="2470829827455850904">"配對裝置"</string>
     <string name="bluetooth_preference_paired_dialog_name_label" msgid="3528740139365123415">"名稱"</string>
     <string name="bluetooth_device_advanced_profile_header_title" msgid="8165093257483965783">"用於"</string>
-    <string name="wifi_ssid_hint" msgid="4155050863239489553">"變更藍牙裝置名稱"</string>
+    <string name="wifi_ssid_hint" msgid="4155050863239489553">"變更藍牙裝置的名稱"</string>
     <string name="bluetooth_notif_ticker" msgid="7192577740198156792">"藍牙配對要求"</string>
     <string name="bluetooth_device_context_pair_connect" msgid="3138105800372470422">"配對並連線"</string>
     <string name="bluetooth" msgid="5235115159234688629">"藍牙"</string>
     <string name="bluetooth_pairing_key_msg" msgid="5066825929751599037">"藍牙配對碼"</string>
-    <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"PIN 中含有字母或符號"</string>
-    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"輸入配對碼,然後按 [返回] 或 Enter 鍵"</string>
+    <string name="bluetooth_enable_alphanumeric_pin" msgid="1636575922217263060">"PIN 碼含有字母或符號"</string>
+    <string name="bluetooth_enter_passkey_msg" msgid="5955236916732265593">"輸入配對碼,然後按下返回鍵或 Enter 鍵"</string>
     <string name="bluetooth_pairing_request" msgid="4769675459526556801">"要與「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」配對嗎?"</string>
-    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"允許「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」存取您的聯絡人和通話記錄"</string>
-    <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"您可能需要在另一部裝置上輸入這個 PIN。"</string>
-    <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"您可能需要在另一部裝置上輸入這個密鑰。"</string>
-    <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"必需為 16 位數字"</string>
-    <string name="bluetooth_pin_values_hint" msgid="1561325817559141687">"一般是 0000 或 1234"</string>
+    <string name="bluetooth_pairing_shares_phonebook" msgid="2015966932886300630">"允許「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」存取你的聯絡人和通話記錄"</string>
+    <string name="bluetooth_enter_pin_other_device" msgid="7825091249522704764">"你可能也必須在另一個裝置上輸入這組 PIN 碼。"</string>
+    <string name="bluetooth_enter_passkey_other_device" msgid="7147248221018865922">"你可能也必須在另一個裝置上輸入這個密碼金鑰。"</string>
+    <string name="bluetooth_pin_values_hint_16_digits" msgid="418776900816984778">"必須是 16 位數的數字"</string>
+    <string name="bluetooth_pin_values_hint" msgid="1561325817559141687">"通常為 0000 或 1234"</string>
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"配對要求"</string>
-    <string name="bluetooth_notif_message" msgid="1060821000510108726">"輕按即可與「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」配對。"</string>
+    <string name="bluetooth_notif_message" msgid="1060821000510108726">"輕觸即可與「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」配對。"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"語言"</string>
     <string name="sound_settings" msgid="3072423952331872246">"音效"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"鈴聲音量"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"導航音量"</string>
@@ -100,69 +99,69 @@
     <string name="media_volume_title" msgid="6697416686272606865">"媒體"</string>
     <string name="media_volume_summary" msgid="2961762827637127239">"設定音樂和影片的音量"</string>
     <string name="alarm_volume_title" msgid="840384014895796587">"鬧鐘"</string>
-    <string name="applications_settings" msgid="794261395191035632">"應用程式資料"</string>
+    <string name="applications_settings" msgid="794261395191035632">"應用程式資訊"</string>
     <string name="disable_text" msgid="4358165448648990820">"停用"</string>
     <string name="enable_text" msgid="1794971777861881238">"啟用"</string>
     <string name="permissions_label" msgid="2701446753515612685">"權限"</string>
     <string name="application_version_label" msgid="8556889839783311649">"版本:%1$s"</string>
-    <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"沒有授予權限"</string>
-    <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"沒有要求權限"</string>
+    <string name="runtime_permissions_summary_no_permissions_granted" msgid="6001439205270250021">"未取得任何授權"</string>
+    <string name="runtime_permissions_summary_no_permissions_requested" msgid="4074220596273432442">"未要求任何權限"</string>
     <string name="data_usage_summary_title" msgid="4368024763485916986">"數據用量"</string>
     <string name="data_usage_app_summary_title" msgid="5012851696585421420">"應用程式數據用量"</string>
     <string name="force_stop" msgid="2153183697014720520">"強制停止"</string>
-    <string name="computing_size" msgid="5791407621793083965">"正在計算…"</string>
+    <string name="computing_size" msgid="5791407621793083965">"計算中…"</string>
     <plurals name="runtime_permissions_additional_count" formatted="false" msgid="3513360187065317613">
-      <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 個其他權限</item>
-      <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 個其他權限</item>
+      <item quantity="other">還有其他 <xliff:g id="COUNT_1">%d</xliff:g> 項權限</item>
+      <item quantity="one">還有其他 <xliff:g id="COUNT_0">%d</xliff:g> 項權限</item>
     </plurals>
     <string name="system_setting_title" msgid="6864599341809463440">"系統"</string>
     <string name="system_update_settings_list_item_title" msgid="5182439376921868735">"系統更新"</string>
     <string name="system_update_settings_list_item_summary" msgid="7395202602021608371"></string>
     <string name="firmware_version" msgid="8491753744549309333">"Android 版本"</string>
-    <string name="security_patch" msgid="4794276590178386903">"Android 安全性修補程式級別"</string>
+    <string name="security_patch" msgid="4794276590178386903">"Android 安全性修補程式等級"</string>
     <string name="model_info" msgid="4966408071657934452">"型號"</string>
     <string name="baseband_version" msgid="2370088062235041897">"基頻版本"</string>
     <string name="kernel_version" msgid="7327212934187011508">"核心版本"</string>
     <string name="build_number" msgid="3997326631001009102">"版本號碼"</string>
-    <string name="device_info_not_available" msgid="2095601973977376655">"未有資料"</string>
+    <string name="device_info_not_available" msgid="2095601973977376655">"無法取得"</string>
     <string name="device_status_activity_title" msgid="4083567497305368200">"狀態"</string>
     <string name="device_status" msgid="267298179806290920">"狀態"</string>
-    <string name="device_status_summary" product="tablet" msgid="600543254608862075">"電池狀態、網絡及其他資訊"</string>
+    <string name="device_status_summary" product="tablet" msgid="600543254608862075">"電池、網路狀態及其他資訊"</string>
     <string name="device_status_summary" product="default" msgid="9130360324418117815">"電話號碼、訊號等。"</string>
     <string name="about_settings" msgid="4329457966672592345">"關於"</string>
     <string name="about_summary" msgid="5374623866267691206">"Android <xliff:g id="VERSION">%1$s</xliff:g>"</string>
-    <string name="about_settings_summary" msgid="7975072809083281401">"查看法律資訊、狀態、軟件版本"</string>
+    <string name="about_settings_summary" msgid="7975072809083281401">"查看法律資訊、狀態、軟體版本"</string>
     <string name="legal_information" msgid="1838443759229784762">"法律資訊"</string>
     <string name="contributors_title" msgid="7698463793409916113">"貢獻者"</string>
     <string name="manual" msgid="4819839169843240804">"手動"</string>
-    <string name="regulatory_labels" msgid="3165587388499646779">"監管標籤"</string>
-    <string name="safety_and_regulatory_info" msgid="1204127697132067734">"安全及監管手冊"</string>
+    <string name="regulatory_labels" msgid="3165587388499646779">"法規標籤"</string>
+    <string name="safety_and_regulatory_info" msgid="1204127697132067734">"安全性和法規手冊"</string>
     <string name="copyright_title" msgid="4220237202917417876">"版權"</string>
     <string name="license_title" msgid="936705938435249965">"授權"</string>
     <string name="terms_title" msgid="5201471373602628765">"條款及細則"</string>
     <string name="webview_license_title" msgid="2531829466541104826">"系統 WebView 授權"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"桌布"</string>
-    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"衛星影像供應商:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
+    <string name="wallpaper_attributions_values" msgid="4292446851583307603">"衛星影像提供者:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
     <string name="settings_license_activity_title" msgid="8499293744313077709">"第三方授權"</string>
     <string name="settings_license_activity_unavailable" msgid="6104592821991010350">"載入授權時發生問題。"</string>
-    <string name="settings_license_activity_loading" msgid="6163263123009681841">"正在載入…"</string>
+    <string name="settings_license_activity_loading" msgid="6163263123009681841">"載入中…"</string>
     <string name="date_and_time_settings_title" msgid="4058492663544475485">"日期和時間"</string>
-    <string name="date_and_time_settings_title_setup_wizard" msgid="7580119979694174107">"設定日期及時間"</string>
-    <string name="date_and_time_settings_summary" msgid="7669856855390804666">"設定日期、時間、時區和格式"</string>
-    <string name="date_time_auto" msgid="3570339569471779767">"自動設定日期和時間"</string>
-    <string name="date_time_auto_summary" msgid="3311706425095342759">"使用網絡提供的時間"</string>
-    <string name="zone_auto" msgid="3701878581920206160">"自動設定時區"</string>
-    <string name="zone_auto_summary" msgid="4345856882906981864">"使用網絡提供的時區"</string>
+    <string name="date_and_time_settings_title_setup_wizard" msgid="7580119979694174107">"設定日期和時間"</string>
+    <string name="date_and_time_settings_summary" msgid="7669856855390804666">"設定日期、時間、時區及時間格式"</string>
+    <string name="date_time_auto" msgid="3570339569471779767">"自動判斷日期和時間"</string>
+    <string name="date_time_auto_summary" msgid="3311706425095342759">"使用網路提供的時間"</string>
+    <string name="zone_auto" msgid="3701878581920206160">"自動判定時區"</string>
+    <string name="zone_auto_summary" msgid="4345856882906981864">"使用網路提供的時區"</string>
     <string name="date_time_24hour_title" msgid="3025576547136168692">"24 小時制"</string>
-    <string name="date_time_24hour" msgid="1137618702556486913">"使用 24 小時制"</string>
+    <string name="date_time_24hour" msgid="1137618702556486913">"使用 24 小時格式"</string>
     <string name="date_time_set_time_title" msgid="5884883050656937853">"時間"</string>
     <string name="date_time_set_time" msgid="6449555153906058248">"設定時間"</string>
     <string name="date_time_set_timezone_title" msgid="3001779256157093425">"時區"</string>
     <string name="date_time_set_timezone" msgid="4759353576185916944">"選取時區"</string>
     <string name="date_time_set_date_title" msgid="6834785820357051138">"日期"</string>
     <string name="date_time_set_date" msgid="2537494485643283230">"設定日期"</string>
-    <string name="zone_list_menu_sort_alphabetically" msgid="7041628618528523514">"按英文字母排序"</string>
-    <string name="zone_list_menu_sort_by_timezone" msgid="4944880536057914136">"按時區排序"</string>
+    <string name="zone_list_menu_sort_alphabetically" msgid="7041628618528523514">"依照字母排序"</string>
+    <string name="zone_list_menu_sort_by_timezone" msgid="4944880536057914136">"依照時區排序"</string>
     <string name="date_picker_title" msgid="1533614225273770178">"日期"</string>
     <string name="time_picker_title" msgid="7436045944320504639">"時間"</string>
     <string name="user_add_user_menu" msgid="5319151436895941496">"新增使用者"</string>
@@ -170,28 +169,26 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"刪除使用者"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"新使用者"</string>
     <string name="user_guest" msgid="3465399481257448601">"訪客"</string>
-    <string name="user_admin" msgid="1535484812908584809">"管理員"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"已使用管理員身分登入"</string>
     <string name="user_switch" msgid="6544839750534690781">"切換"</string>
-    <string name="current_user_name" msgid="3813671533249316823">"您 (%1$s)"</string>
+    <string name="current_user_name" msgid="3813671533249316823">"你 (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"名稱"</string>
-    <string name="user_summary_not_set_up" msgid="1473688119241224145">"未設定"</string>
+    <string name="user_summary_not_set_up" msgid="1473688119241224145">"尚未設定"</string>
     <string name="edit_user_name_title" msgid="6890782937520262478">"編輯使用者名稱"</string>
     <string name="users_list_title" msgid="770764290290240909">"使用者"</string>
     <string name="accounts_settings_title" msgid="436190037084293471">"帳戶"</string>
     <string name="user_details_title" msgid="1104762783367701498">"使用者"</string>
     <string name="no_accounts_added" msgid="5148163140691096055">"未新增任何帳戶"</string>
-    <string name="account_list_title" msgid="7631588514613843065">"<xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g>的帳戶"</string>
-    <string name="account_details_title" msgid="7529571432258448573">"帳戶資料"</string>
+    <string name="account_list_title" msgid="7631588514613843065">"<xliff:g id="CURRENT_USER_NAME">%1$s</xliff:g> 的帳戶"</string>
+    <string name="account_details_title" msgid="7529571432258448573">"帳戶資訊"</string>
     <string name="add_account_title" msgid="5988746086885210040">"新增帳戶"</string>
     <string name="add_an_account" msgid="1072285034300995091">"新增帳戶"</string>
-    <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"受限設定檔無法新增帳戶"</string>
+    <string name="user_cannot_add_accounts_message" msgid="6775605884544906797">"設有限制的個人資料無法新增帳戶"</string>
     <string name="remove_account_title" msgid="8840386525787836381">"移除帳戶"</string>
     <string name="really_remove_account_title" msgid="3555164432587924900">"要移除帳戶嗎?"</string>
-    <string name="really_remove_account_message" msgid="4296769280849579900">"移除此帳戶後,裝置上的訊息、聯絡人和其他資料將全部刪除!"</string>
-    <string name="remove_account_failed" msgid="7472511529086294087">"您的管理員不允許這項變更"</string>
+    <string name="really_remove_account_message" msgid="4296769280849579900">"移除帳戶後,裝置上所有的訊息、聯絡人和其他資料將全部遭到刪除!"</string>
+    <string name="remove_account_failed" msgid="7472511529086294087">"你的管理員不允許這項變更"</string>
     <string name="really_remove_user_title" msgid="4990029019291756762">"要移除這位使用者嗎?"</string>
-    <string name="really_remove_user_message" msgid="3828090902833944533">"所有應用程式和資料都會被刪除。"</string>
+    <string name="really_remove_user_message" msgid="3828090902833944533">"所有應用程式和資料都會遭到刪除。"</string>
     <string name="remove_user_error_title" msgid="2038275458657689420">"無法移除使用者。"</string>
     <string name="remove_user_error_message" msgid="6803947507134323358">"要再試一次嗎?"</string>
     <string name="remove_user_error_dismiss" msgid="4006591159426844335">"關閉"</string>
@@ -202,93 +199,91 @@
     <string name="security_settings_title" msgid="6955331714774709746">"安全性"</string>
     <string name="security_settings_subtitle" msgid="2244635550239273229">"螢幕鎖定"</string>
     <string name="security_lock_none" msgid="1054645093754839638">"無"</string>
-    <string name="security_lock_pattern" msgid="1174352995619563104">"鎖定圖案"</string>
+    <string name="security_lock_pattern" msgid="1174352995619563104">"圖案"</string>
     <string name="security_lock_pin" msgid="4891899974369503200">"PIN"</string>
     <string name="security_lock_password" msgid="4420203740048322494">"密碼"</string>
-    <string name="lock_settings_picker_title" msgid="6590330165050361632">"請選擇上鎖方式"</string>
+    <string name="lock_settings_picker_title" msgid="6590330165050361632">"選擇鎖定類型"</string>
     <string name="screen_lock_options" msgid="7023338635352915768">"螢幕鎖定選項"</string>
     <string name="lock_settings_enter_pattern" msgid="4826034565853171624">"畫出您的圖案"</string>
     <string name="lockpattern_confirm_button_text" msgid="7784925958324484965">"確認"</string>
-    <string name="lockpattern_restart_button_text" msgid="9355771277617537">"再畫一次"</string>
+    <string name="lockpattern_restart_button_text" msgid="9355771277617537">"重畫"</string>
     <string name="continue_button_text" msgid="5129979170426836641">"繼續"</string>
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"重試"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"略過"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"設定螢幕鎖定"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"選擇 PIN"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"選擇圖案"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"選擇密碼"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"目前的螢幕鎖定方式"</string>
-    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"為安全起見,請設定鎖定圖案"</string>
+    <string name="choose_lock_pattern_message" msgid="6242765203541309524">"為了安全起見,請設定圖案"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"清除"</string>
     <string name="lockpattern_cancel_button_text" msgid="4068764595622381766">"取消"</string>
-    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"新解鎖圖案"</string>
+    <string name="lockpattern_pattern_confirmed" msgid="5984306638250515385">"你的新解鎖圖案"</string>
     <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"畫出解鎖圖案"</string>
-    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"完成後請移開手指"</string>
+    <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"完成時請移開手指"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"已記錄圖案"</string>
-    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"請再次畫出圖案以確認"</string>
-    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"圖案至少要連接 4 點,請再試一次。"</string>
+    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"請再畫一次,以確認圖案無誤"</string>
+    <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"圖案至少需連接 4 點,請再試一次。"</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"圖案錯誤"</string>
     <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"如何畫出解鎖圖案"</string>
-    <string name="error_saving_lockpattern" msgid="2933512812768570130">"儲存鎖定圖案時發生錯誤"</string>
+    <string name="error_saving_lockpattern" msgid="2933512812768570130">"儲存圖案時發生錯誤"</string>
     <string name="okay" msgid="4589873324439764349">"確定"</string>
     <string name="remove_screen_lock_title" msgid="1234382338764193387">"要移除螢幕鎖定嗎?"</string>
     <string name="remove_screen_lock_message" msgid="6675850371585564965">"這樣,任何人都能存取您的帳戶"</string>
     <string name="lock_settings_enter_pin" msgid="1669172111244633904">"請輸入 PIN"</string>
     <string name="lock_settings_enter_password" msgid="2636669926649496367">"請輸入密碼"</string>
-    <string name="choose_lock_pin_message" msgid="2963792070267774417">"為安全起見,請設定 PIN"</string>
-    <string name="confirm_your_pin_header" msgid="9096581288537156102">"請重新輸入 PIN"</string>
-    <string name="choose_lock_pin_hints" msgid="7362906249992020844">"PIN 必需有至少 4 個數字"</string>
+    <string name="choose_lock_pin_message" msgid="2963792070267774417">"為了安全起見,請設定 PIN 碼"</string>
+    <string name="confirm_your_pin_header" msgid="9096581288537156102">"請重新輸入 PIN 碼"</string>
+    <string name="choose_lock_pin_hints" msgid="7362906249992020844">"PIN 碼至少要有 4 個數字"</string>
     <string name="lockpin_invalid_pin" msgid="2149191577096327424">"PIN 無效,必須至少有 4 位數字。"</string>
-    <string name="confirm_pins_dont_match" msgid="4607110139373520720">"PIN 不符"</string>
-    <string name="error_saving_lockpin" msgid="9011960139736000393">"儲存 PIN 時發生錯誤"</string>
+    <string name="confirm_pins_dont_match" msgid="4607110139373520720">"PIN 碼不符"</string>
+    <string name="error_saving_lockpin" msgid="9011960139736000393">"儲存 PIN 碼時發生錯誤"</string>
     <string name="lockscreen_wrong_pin" msgid="4922465731473805306">"PIN 錯誤"</string>
     <string name="lockscreen_wrong_password" msgid="5757087577162231825">"密碼錯誤"</string>
-    <string name="choose_lock_password_message" msgid="6124341145027370784">"為安全起見,請設定密碼"</string>
+    <string name="choose_lock_password_message" msgid="6124341145027370784">"為了安全起見,請設定密碼"</string>
     <string name="confirm_your_password_header" msgid="7052891840366724938">"請重新輸入密碼"</string>
     <string name="confirm_passwords_dont_match" msgid="7300229965206501753">"密碼不符"</string>
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"清除"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"取消"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"確認"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"必須包含至少 4 個字元"</string>
-    <string name="lockpassword_password_too_short" msgid="6681218025001328405">"必需有至少 <xliff:g id="COUNT">%d</xliff:g> 個字元"</string>
-    <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN 必需有至少 <xliff:g id="COUNT">%d</xliff:g> 個數字"</string>
-    <string name="lockpassword_password_too_long" msgid="7530214940279491291">"必需少於 <xliff:g id="NUMBER">%d</xliff:g> 個字元"</string>
-    <string name="lockpassword_pin_too_long" msgid="62957683396974404">"必需少於 <xliff:g id="NUMBER">%d</xliff:g> 個數字"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"密碼長度必須介於 4 到 8 個字元之間,當中要包含至少 1 個數字"</string>
+    <string name="lockpassword_password_too_short" msgid="6681218025001328405">"至少要有 <xliff:g id="COUNT">%d</xliff:g> 個字元"</string>
+    <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN 碼至少要有 <xliff:g id="COUNT">%d</xliff:g> 個數字"</string>
+    <string name="lockpassword_password_too_long" msgid="7530214940279491291">"長度必須少於 <xliff:g id="NUMBER">%d</xliff:g> 個字元"</string>
+    <string name="lockpassword_pin_too_long" msgid="62957683396974404">"長度必須少於 <xliff:g id="NUMBER">%d</xliff:g> 個數字"</string>
     <string name="lockpassword_pin_contains_non_digits" msgid="3044526271686839923">"只可包含數字 0-9。"</string>
-    <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"裝置管理員不允許使用最近用過的 PIN"</string>
-    <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"您的 IT 管理員已禁止使用常用的 PIN,請嘗試輸入另一個 PIN。"</string>
+    <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"裝置管理員不允許使用最近用過的 PIN 碼"</string>
+    <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"你的 IT 管理員已封鎖常見 PIN 碼,請改用其他 PIN 碼。"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"不可包含無效字元。"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"密碼無效,必須包含至少 4 個字元。"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"密碼無效。長度必須介乎 4 至 8 個字元之間,並包含至少 1 個數字和 1 個字母;不可使用空白字元。"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
-      <item quantity="other">必需包含至少 <xliff:g id="COUNT">%d</xliff:g> 個字母</item>
-      <item quantity="one">必需包含至少 1 個字母</item>
+      <item quantity="other">至少必須包含 <xliff:g id="COUNT">%d</xliff:g> 個字母</item>
+      <item quantity="one">至少必須包含 1 個字母</item>
     </plurals>
     <plurals name="lockpassword_password_requires_lowercase" formatted="false" msgid="2267487180744744833">
-      <item quantity="other">必需包含至少 <xliff:g id="COUNT">%d</xliff:g> 個小寫字母</item>
-      <item quantity="one">必需包含至少 1 個小寫字母</item>
+      <item quantity="other">至少必須包含 <xliff:g id="COUNT">%d</xliff:g> 個小寫字母</item>
+      <item quantity="one">至少必須包含 1 個小寫字母</item>
     </plurals>
     <plurals name="lockpassword_password_requires_uppercase" formatted="false" msgid="7999264563026517898">
-      <item quantity="other">必需包含至少 <xliff:g id="COUNT">%d</xliff:g> 個大寫字母</item>
-      <item quantity="one">必需包含至少 1 個大寫字母</item>
+      <item quantity="other">至少必須包含 <xliff:g id="COUNT">%d</xliff:g> 個大寫字母</item>
+      <item quantity="one">至少必須包含 1 個大寫字母</item>
     </plurals>
     <plurals name="lockpassword_password_requires_numeric" formatted="false" msgid="7935079851855168646">
-      <item quantity="other">必需包含至少 <xliff:g id="COUNT">%d</xliff:g> 個數字</item>
-      <item quantity="one">必需包含至少 1 個數字</item>
+      <item quantity="other">至少必須包含 <xliff:g id="COUNT">%d</xliff:g> 個數字</item>
+      <item quantity="one">至少必須包含 1 個數字</item>
     </plurals>
     <plurals name="lockpassword_password_requires_symbols" formatted="false" msgid="3994046435150094132">
-      <item quantity="other">必需包含至少 <xliff:g id="COUNT">%d</xliff:g> 個特別符號</item>
-      <item quantity="one">必需包含至少 1 個特別符號</item>
+      <item quantity="other">至少必須包含 <xliff:g id="COUNT">%d</xliff:g> 個特殊符號</item>
+      <item quantity="one">至少必須包含 1 個特殊符號</item>
     </plurals>
     <plurals name="lockpassword_password_requires_nonletter" formatted="false" msgid="6878486326748506524">
-      <item quantity="other">必需包含至少 <xliff:g id="COUNT">%d</xliff:g> 個非字母字元</item>
-      <item quantity="one">必需包含至少 1 個非字母字元</item>
+      <item quantity="other">至少必須包含 <xliff:g id="COUNT">%d</xliff:g> 個非字母字元</item>
+      <item quantity="one">至少必須包含 1 個非字母字元</item>
     </plurals>
     <string name="lockpassword_password_recently_used" msgid="8255729487108602924">"裝置管理員不允許使用最近用過的密碼"</string>
     <string name="error_saving_password" msgid="8334882262622500658">"儲存密碼時發生錯誤"</string>
-    <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"您的 IT 管理員已禁止使用常用的密碼,請嘗試輸入另一組密碼。"</string>
+    <string name="lockpassword_password_blacklisted_by_admin" msgid="7965893810326503891">"你的 IT 管理員已封鎖常見密碼,請改用其他密碼。"</string>
     <string name="lockpassword_pin_no_sequential_digits" msgid="38813552228809240">"不可使用依遞增或遞減順序排列或重複的連續數字。"</string>
     <string name="setup_lock_settings_options_button_label" msgid="3337845811029780896">"螢幕鎖定選項"</string>
-    <string name="forget" msgid="3971143908183848527">"忘記"</string>
+    <string name="forget" msgid="3971143908183848527">"清除"</string>
     <string name="delete_button" msgid="5840500432614610850">"刪除"</string>
     <string name="remove_button" msgid="6664656962868194178">"移除"</string>
     <string name="cancel" msgid="750286395700355455">"取消"</string>
@@ -300,6 +295,7 @@
     <string name="exit_retail_mode_dialog_confirmation_button_text" msgid="3147249675355968649">"結束示範模式"</string>
     <string name="suggestion_primary_button" msgid="6421115494714083020">"完成設定"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"暫時不要"</string>
-    <string name="restricted_while_driving" msgid="6217369093121968299">"無法在駕駛時使用此功能。"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"無法在駕駛時新增使用者。"</string>
+    <string name="restricted_while_driving" msgid="6217369093121968299">"開車時無法使用這項功能。"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 88a785c..3e21116 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"配對要求"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"輕觸即可與「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」配對。"</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"語言"</string>
     <string name="sound_settings" msgid="3072423952331872246">"音效"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"鈴聲音量"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"導航音量"</string>
@@ -140,7 +139,7 @@
     <string name="copyright_title" msgid="4220237202917417876">"版權"</string>
     <string name="license_title" msgid="936705938435249965">"授權"</string>
     <string name="terms_title" msgid="5201471373602628765">"條款及細則"</string>
-    <string name="webview_license_title" msgid="2531829466541104826">"System WebView 授權"</string>
+    <string name="webview_license_title" msgid="2531829466541104826">"系統 WebView 授權"</string>
     <string name="wallpaper_attributions" msgid="9201272150014500697">"桌布"</string>
     <string name="wallpaper_attributions_values" msgid="4292446851583307603">"衛星影像提供者:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky"</string>
     <string name="settings_license_activity_title" msgid="8499293744313077709">"第三方授權"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"刪除使用者"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"新使用者"</string>
     <string name="user_guest" msgid="3465399481257448601">"訪客"</string>
-    <string name="user_admin" msgid="1535484812908584809">"管理員"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"登入身分:管理員"</string>
     <string name="user_switch" msgid="6544839750534690781">"切換"</string>
     <string name="current_user_name" msgid="3813671533249316823">"你 (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"名稱"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"重試"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"略過"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"設定螢幕鎖定"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"選擇 PIN 碼"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"選擇圖案"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"選擇密碼"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"目前的螢幕鎖定方式"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"為了安全起見,請設定圖案"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"清除"</string>
@@ -225,7 +220,7 @@
     <string name="lockpattern_recording_intro_header" msgid="7864149726033694408">"畫出解鎖圖案"</string>
     <string name="lockpattern_recording_inprogress" msgid="1575019990484725964">"完成時請移開手指"</string>
     <string name="lockpattern_pattern_entered" msgid="6103071005285320575">"已記錄圖案"</string>
-    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"請再次畫出剛剛的圖案,以完成確認程序"</string>
+    <string name="lockpattern_need_to_confirm" msgid="4648070076022940382">"請再畫一次,以確認圖案無誤"</string>
     <string name="lockpattern_recording_incorrect_too_short" msgid="2417932185815083082">"圖案至少需連接 4 點,請再試一次。"</string>
     <string name="lockpattern_pattern_wrong" msgid="929223969555399363">"圖案錯誤"</string>
     <string name="lockpattern_settings_help_how_to_record" msgid="4436556875843192284">"如何畫出解鎖圖案"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"清除"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"取消"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"確認"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"至少要有 4 個字元"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"密碼長度必須介於 4 到 8 個字元之間,當中要包含至少 1 個數字"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"至少要有 <xliff:g id="COUNT">%d</xliff:g> 個字元"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"PIN 碼至少要有 <xliff:g id="COUNT">%d</xliff:g> 個數字"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"長度必須少於 <xliff:g id="NUMBER">%d</xliff:g> 個字元"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"裝置管理員不允許使用最近用過的 PIN 碼"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"你的 IT 管理員已封鎖常見 PIN 碼,請改用其他 PIN 碼。"</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"不得包含無效字元。"</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"密碼無效,至少要有 4 個字元。"</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"密碼無效。長度必須介於 4 到 8 個字元之間,並包含至少 1 個數字和 1 個字母;不得使用空白字元。"</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="other">至少必須包含 <xliff:g id="COUNT">%d</xliff:g> 個字母</item>
       <item quantity="one">至少必須包含 1 個字母</item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"完成設定"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"暫時不要"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"開車時無法使用這項功能。"</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"無法在開車期間新增使用者。"</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 00a843e..db9f6dc 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -91,7 +91,6 @@
     <string name="bluetooth_notif_title" msgid="8374602799367803335">"Isicelo sokubhangqa"</string>
     <string name="bluetooth_notif_message" msgid="1060821000510108726">"Thepha ukuze ubhanqe ne-<xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_error_title" msgid="2341600997536511742"></string>
-    <string name="language_settings" msgid="2079258598337245546">"Izilimi"</string>
     <string name="sound_settings" msgid="3072423952331872246">"Umsindo"</string>
     <string name="ring_volume_title" msgid="3135241004980719442">"Ivolumu yokukhalisa"</string>
     <string name="navi_volume_title" msgid="946292066759195165">"Ivolomu yokuzula"</string>
@@ -170,8 +169,6 @@
     <string name="user_delete_user_description" msgid="2300280525351142435">"Susa umsebenzisi"</string>
     <string name="user_new_user_name" msgid="7115771396412339662">"Umsebenzisi omusha"</string>
     <string name="user_guest" msgid="3465399481257448601">"Isivakashi"</string>
-    <string name="user_admin" msgid="1535484812908584809">"Mqondisi"</string>
-    <string name="signed_in_admin_user" msgid="1267225622818673274">"Ungene ngemvume njengomlawuli"</string>
     <string name="user_switch" msgid="6544839750534690781">"Shintsha"</string>
     <string name="current_user_name" msgid="3813671533249316823">"Wena (%1$s)"</string>
     <string name="user_name_label" msgid="3210832645046206845">"Igama"</string>
@@ -214,9 +211,7 @@
     <string name="lockscreen_retry_button_text" msgid="5314212350698701242">"Zama futhi"</string>
     <string name="lockscreen_skip_button_text" msgid="3755748786396198091">"Yeqa"</string>
     <string name="set_screen_lock" msgid="5239317292691332780">"Setha ukukhiya kwesikrini"</string>
-    <string name="lockscreen_choose_your_pin" msgid="1645229555410061526">"Khetha i-PIN yakho"</string>
     <string name="lockscreen_choose_your_pattern" msgid="6801175111142593404">"Khetha iphathini yakho"</string>
-    <string name="lockscreen_choose_your_password" msgid="5703747830308181428">"Khetha iphasiwedi yakho"</string>
     <string name="current_screen_lock" msgid="637651611145979587">"Ukukhiya isikrini kwamanje"</string>
     <string name="choose_lock_pattern_message" msgid="6242765203541309524">"Ngokuphepha, setha iphethini"</string>
     <string name="lockpattern_retry_button_text" msgid="4655398824001857843">"Sula"</string>
@@ -249,7 +244,7 @@
     <string name="lockpassword_clear_label" msgid="6363680971025188064">"Sula"</string>
     <string name="lockpassword_cancel_label" msgid="5791237697404166450">"Khansela"</string>
     <string name="lockpassword_confirm_label" msgid="5918463281546146953">"Qinisekisa"</string>
-    <string name="choose_lock_password_hints" msgid="3903696950202491593">"Kumele okungenani ibe izinhlamvu ezingu-4"</string>
+    <string name="choose_lock_password_hints" msgid="1802962836351866087">"Iphasiwedi kufanele ibe phakathi kwezinhlamvu ezingi-4-8 okungenani inombolo engu-1"</string>
     <string name="lockpassword_password_too_short" msgid="6681218025001328405">"Kumele okungenani kube izinhlamvu ezingu-<xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_pin_too_short" msgid="6363004004424904218">"Iphinikhodi kumele ibe okungenani amadijithi angu-<xliff:g id="COUNT">%d</xliff:g>"</string>
     <string name="lockpassword_password_too_long" msgid="7530214940279491291">"Kumele ibe izinhlamvu ezincane kunezingu-<xliff:g id="NUMBER">%d</xliff:g>"</string>
@@ -258,7 +253,7 @@
     <string name="lockpassword_pin_recently_used" msgid="7901918311213276207">"Umlawuli wedivayisi akavumeli ukusebenzisa iphinikhodi yakamuva"</string>
     <string name="lockpassword_pin_blacklisted_by_admin" msgid="7412709707800738442">"Ama-PIN avamile avinjelwe umlawuli wakho we-IT. Zama i-PIN ehlukile."</string>
     <string name="lockpassword_illegal_character" msgid="1984970060523635618">"Lokhu akukwazi ukufaka uhlamvu olungavumelekile."</string>
-    <string name="lockpassword_invalid_password" msgid="1690956113717418430">"Iphasiwedi ayivumelekile, kumele okungenani ibe izinhlamvu ezingu-4."</string>
+    <string name="lockpassword_invalid_password" msgid="4987689810161043367">"Iphasiwedi ayivumelekile, kumele ibe izinhlamvu ezingu-4.-8, iqukethe okungenani idijithi elingu-1, uhlamvu olungu-1, asikho isikhala esimhlophe."</string>
     <plurals name="lockpassword_password_requires_letters" formatted="false" msgid="424616259312760303">
       <item quantity="one">Kumele okungenani iqukathe amagama angu-<xliff:g id="COUNT">%d</xliff:g></item>
       <item quantity="other">Kumele okungenani iqukathe amagama angu-<xliff:g id="COUNT">%d</xliff:g></item>
@@ -301,5 +296,6 @@
     <string name="suggestion_primary_button" msgid="6421115494714083020">"qedela ukusetha"</string>
     <string name="suggestion_secondary_button" msgid="7075088546904464681">"hhayi manje"</string>
     <string name="restricted_while_driving" msgid="6217369093121968299">"Isici asitholakali ngenkathi ushayela."</string>
-    <string name="add_user_restricted_while_driving" msgid="6384004350628271556">"Ayikwazi ukungeza umsebenzisi ngenkathi ushayela."</string>
+    <!-- no translation found for add_user_restricted_while_driving (6384004350628271556) -->
+    <skip />
 </resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..62cb3be
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<resources>
+
+    <!-- Values for premium SMS permission selector [CHAR LIMIT=30] -->
+    <string-array name="premium_sms_access_values">
+        <!-- Ask user before sending to premium SMS short code. -->
+        <item>Ask</item>
+        <!-- Never allow app to send to premium SMS short code. -->
+        <item>Never allow</item>
+        <!-- Always allow app to send to premium SMS short code. -->
+        <item>Always allow</item>
+    </string-array>
+
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 9ed19a7..c22af54 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -1,23 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 2018, 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.
-*/
+    Copyright 2018 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.
 -->
 
-<!-- Defines the attributes and values used in res/xml/car_volume_items.xml -->
 <resources>
     <declare-styleable name="carVolumeItems"/>
 
@@ -45,11 +42,44 @@
         </attr>
 
         <!-- Title and icon resource ids to render on UI -->
-        <attr name="title" format="reference"/>
+        <attr name="titleText" format="reference"/>
         <attr name="icon" format="reference"/>
     </declare-styleable>
 
     <declare-styleable name="PinPadView">
-        <attr name="enterKeyDrawable" format="reference"/>
+        <attr name="layout" format="reference"/>
     </declare-styleable>
+
+    <declare-styleable name="Preference">
+        <!-- Classname of a PreferenceController corresponding to the preference -->
+        <attr name="controller" format="string"/>
+    </declare-styleable>
+
+    <declare-styleable name="TwoActionPreference">
+        <!-- Determines if the secondary action is initially shown -->
+        <attr name="actionShown" format="boolean"/>
+    </declare-styleable>
+
+    <declare-styleable name="ProgressBarPreference">
+        <attr name="min" format="integer"/>
+        <attr name="max" format="integer"/>
+        <attr name="progress" format="integer"/>
+        <attr name="minLabel" format="string"/>
+        <attr name="maxLabel" format="string"/>
+    </declare-styleable>
+
+    <attr name="wifiSignalColor" format="color"/>
+    <attr name="iconColor" format="color"/>
+    <attr name="dividerColor" format="color"/>
+    <attr name="userSwitcherBackground" format="reference"/>
+    <attr name="userSwitcherCurrentUserColor" format="color"/>
+    <attr name="userSwitcherAddIconColor" format="color"/>
+    <attr name="userSwitcherNameTextAppearance" format="reference"/>
+    <attr name="userSwitcherAddIconBackgroundColor" format="color"/>
+    <attr name="quickSettingsEnabledColor" format="color"/>
+    <attr name="quickSettingsDisabledColor" format="color"/>
+    <attr name="quickSettingsIconEnabledColor" format="color"/>
+    <attr name="quickSettingsIconDisabledColor" format="color"/>
+    <attr name="suggestionsPrimaryColor" format="color"/>
+    <attr name="suggestionsSecondaryColor" format="color"/>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
deleted file mode 100644
index 4197d04..0000000
--- a/res/values/colors.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 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.
--->
-<resources>
-    <color name="lock_pattern_background">#353839</color>
-
-    <color name="toggle_bg_disabled">@color/car_grey_400</color>
-    <color name="toggle_icon_disabled">@color/car_grey_800</color>
-    <color name="seekbar_track">@color/car_grey_700</color>
-
-    <color name="google_blue_600_dark">#1A73E8</color>
-    <color name="google_blue_600_light">#2581DF</color>
-    <color name="google_blue_600">@color/google_blue_600_dark</color>
-
-    <!-- colors for user switcher -->
-    <color name="car_user_switcher_background_color">@color/car_card</color>
-    <color name="car_user_switcher_name_text_color">@color/car_body1</color>
-    <color name="car_user_switcher_add_user_background_color">@color/car_grey_200</color>
-    <color name="car_user_switcher_add_user_add_sign_color">@color/car_body1</color>
-    <color name="car_user_switcher_current_user_color">@color/car_teal_700</color>
-</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..21b7908
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<resources>
+    <!-- The fully qualified class name of the fragment to launch when the settings application starts. -->
+    <string name="config_settings_hierarchy_root_fragment" translatable="false">com.android.car.settings.quicksettings.QuickSettingFragment</string>
+    <!-- Whether system_update_settings should be shown or not. -->
+    <bool name="config_show_system_update_settings">true</bool>
+    <!-- Whether Wi-Fi Mac address should be shown or not. -->
+    <bool name="config_show_wifi_mac_address">true</bool>
+    <!-- Whether to show regulatory info or not. -->
+    <bool name="config_show_regulatory_info">true</bool>
+    <!-- Whether premium SMS should be shown or not. -->
+    <bool name="config_show_premium_sms">true</bool>
+
+    <!-- The component which listens for the enabling of developer options. -->
+    <string name="config_dev_options_module" translatable="false">com.android.car.developeroptions/.Settings$DevelopmentSettingsDashboardActivity</string>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 9105065..21b0abd 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -1,51 +1,132 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!--
+    Copyright 2018 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
+    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
+         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.
+    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.
 -->
 
-
 <resources>
-    <dimen name="seekbar_track_height">6dp</dimen>
-    <dimen name="seekbar_track_corner">3dp</dimen>
-    <dimen name="seekbar_padding_top">10dp</dimen>
-    <dimen name="switch_end_padding">5dp</dimen>
-    <dimen name="double_icon_size">112dp</dimen>
+    <dimen name="icon_size">@*android:dimen/car_primary_icon_size</dimen>
+    <dimen name="touch_target_size">@*android:dimen/car_touch_target_size</dimen>
+    <dimen name="divider_height">@*android:dimen/car_list_divider_height</dimen>
+    <dimen name="divider_inset_left">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="divider_inset_right">@*android:dimen/car_keyline_1</dimen>
 
-    <dimen name="action_bar_end_widget_margin_end">46dp</dimen>
-    <dimen name="optical_center_offset">1dp</dimen>
+    <dimen name="button_min_width">@*android:dimen/car_button_min_width</dimen>
+    <dimen name="button_fading_edge_length">40dp</dimen>
+    <dimen name="button_ripple_radius">@*android:dimen/car_radius_1</dimen>
 
-    <dimen name="tile_top_bottom_padding">15dp</dimen>
-    <dimen name="line_item_top_margin">25dp</dimen>
+    <!-- Action Bar -->
+    <dimen name="action_bar_height">@*android:dimen/car_app_bar_height</dimen>
 
-    <integer name="bluetooth_name_length">32</integer>
+    <!-- Suggestions -->
+    <dimen name="suggestions_top_bottom_margin">@*android:dimen/car_padding_4</dimen>
+    <dimen name="suggestions_corner_radius">@*android:dimen/car_radius_3</dimen>
+    <!-- Padding between the top of the card and the text -->
+    <dimen name="suggestions_text_padding_top">@*android:dimen/car_padding_2</dimen>
+    <!-- Padding between the bottom of the text and the action bar -->
+    <dimen name="suggestions_text_padding_bottom">@*android:dimen/car_padding_2</dimen>
+    <!-- The margin between the main suggestion text and the subtext -->
+    <dimen name="suggestions_subtext_margin_top">@*android:dimen/car_padding_1</dimen>
+    <dimen name="suggestions_padding_start">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="suggestions_padding_end">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="suggestions_action_bar_height">@*android:dimen/car_card_action_bar_height</dimen>
 
+    <!-- Quick Settings -->
+    <dimen name="brightness_seekbar_margin_bottom">@*android:dimen/car_padding_4</dimen>
+    <dimen name="brightness_seekbar_height">@*android:dimen/car_single_line_list_item_height</dimen>
+    <dimen name="brightness_seekbar_track_height">6dp</dimen>
+    <dimen name="brightness_seekbar_track_corner">3dp</dimen>
+    <dimen name="brightness_knob_size">44dp</dimen>
+    <dimen name="tile_margin_start">@*android:dimen/car_padding_2</dimen>
+    <dimen name="tile_margin_end">@*android:dimen/car_padding_2</dimen>
+    <dimen name="tile_margin_bottom">@*android:dimen/car_padding_5</dimen>
+    <dimen name="tile_padding">10dp</dimen>
+    <dimen name="tile_icon_margin_bottom">@*android:dimen/car_padding_4</dimen>
+    <dimen name="circle_ripple_bg_radius">90dp</dimen>
+
+    <!-- Bluetooth -->
+    <dimen name="bluetooth_pin_dialog_margin_start">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="bluetooth_pin_dialog_margin_end">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="bluetooth_pin_dialog_section_height">
+        @*android:dimen/car_single_line_list_item_height
+    </dimen>
+    <dimen name="bluetooth_pin_dialog_text_margin_start">@*android:dimen/car_keyline_3</dimen>
+    <dimen name="bluetooth_pin_dialog_text_margin_end">@*android:dimen/car_keyline_3</dimen>
+    <dimen name="bluetooth_pin_dialog_subtext_margin_top">@*android:dimen/car_padding_1</dimen>
+
+    <!-- Edit Username -->
+    <dimen name="edit_username_padding_start">@*android:dimen/car_margin</dimen>
+    <dimen name="edit_username_padding_end">@*android:dimen/car_margin</dimen>
+    <dimen name="edit_username_text_padding_start">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="edit_username_text_padding_end">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="edit_username_text_padding_top">@*android:dimen/car_padding_3</dimen>
+
+    <!-- Security -->
+    <dimen name="choose_title_text_margin_bottom">@*android:dimen/car_padding_2</dimen>
+    <dimen name="choose_pin_title_text_margin_bottom">@*android:dimen/car_padding_5</dimen>
+    <dimen name="pin_password_entry_padding_horizontal">@*android:dimen/car_padding_2</dimen>
     <dimen name="pin_pad_key_width">120dp</dimen>
     <dimen name="pin_pad_key_height">80dp</dimen>
     <dimen name="pin_pad_key_margin">12dp</dimen>
     <dimen name="confirm_pattern_dimension">350dp</dimen>
-    <dimen name="pin_pad_icon_size">32dp</dimen>
+    <dimen name="confirm_lock_message_vertical_spacing">@*android:dimen/car_padding_2</dimen>
 
-    <dimen name="toggle_padding">10dp</dimen>
-    <dimen name="toggle_ripple">90dp</dimen>
-    <dimen name="brightness_knob_size">44dp</dimen>
+    <!-- User Switcher -->
+    <dimen name="user_switcher_image_avatar_size">96dp</dimen>
+    <dimen name="user_switcher_vertical_spacing_between_users">@*android:dimen/car_padding_5
+    </dimen>
+    <dimen name="user_switcher_vertical_spacing_between_name_and_avatar">
+        @*android:dimen/car_padding_4
+    </dimen>
+    <dimen name="user_switcher_current_user_circle_width">126dp</dimen>
+    <dimen name="user_switcher_current_user_circle_stroke_width">7dp</dimen>
 
-    <!-- dimensions for the car user switcher -->
-    <dimen name="car_user_switcher_name_text_size">@dimen/car_body1_size</dimen>
-    <dimen name="car_user_switcher_image_avatar_size">@dimen/car_large_avatar_size</dimen>
-    <dimen name="car_user_switcher_vertical_spacing_between_users">@dimen/car_padding_5</dimen>
-    <dimen name="car_user_switcher_vertical_spacing_between_name_and_avatar">@dimen/car_padding_4</dimen>
-    <dimen name="car_user_switcher_margin_top">@dimen/car_padding_4</dimen>
-    <dimen name="car_user_switcher_current_user_circle_width">126dp</dimen>
-    <dimen name="car_user_switcher_current_user_circle_stroke_width">7dp</dimen>
-</resources>
\ No newline at end of file
+    <!-- Preferences -->
+    <dimen name="preference_padding_top">@dimen/car_padding_2</dimen>
+    <dimen name="preference_padding_bottom">@dimen/car_padding_2</dimen>
+    <dimen name="two_action_preference_divider_width">1dp</dimen>
+    <dimen name="usage_indicator_preference_title_margin_top">@*android:dimen/car_padding_2</dimen>
+    <dimen name="usage_indicator_preference_title_margin_bottom">@*android:dimen/car_padding_2</dimen>
+    <dimen name="usage_indicator_preference_progressbar_height">32dp</dimen>
+    <dimen name="usage_indicator_preference_label_margin_top">@*android:dimen/car_padding_1</dimen>
+    <dimen name="usage_indicator_preference_label_margin_bottom">@*android:dimen/car_padding_4</dimen>
+    <dimen name="usage_indicator_preference_summary_margin_bottom">@*android:dimen/car_padding_2</dimen>
+    <dimen name="data_usage_summary_preference_padding_bottom">@*android:dimen/car_padding_2</dimen>
+    <dimen name="data_usage_summary_preference_button_margin_top">@*android:dimen/car_padding_2</dimen>
+    <dimen name="data_usage_summary_preference_button_height">@*android:dimen/car_button_height</dimen>
+    <dimen name="data_usage_summary_preference_button_min_width">@*android:dimen/car_button_min_width</dimen>
+    <dimen name="progress_bar_preference_progressbar_margin_top">@*android:dimen/car_padding_1</dimen>
+    <dimen name="progress_bar_preference_label_margin_top">@*android:dimen/car_padding_0</dimen>
+    <dimen name="progress_bar_preference_padding_top">@*android:dimen/car_padding_1</dimen>
+    <dimen name="progress_bar_preference_padding_bottom">@*android:dimen/car_padding_1</dimen>
+
+    <!-- Data usage -->
+    <dimen name="usage_number_text_size">36sp</dimen>
+    <dimen name="usage_pickers_margin_vertical">@*android:dimen/car_padding_2</dimen>
+    <dimen name="usage_pickers_text_margin_end">@*android:dimen/car_padding_5</dimen>
+    <dimen name="usage_bytes_picker_margin_horizontal">@*android:dimen/car_padding_5</dimen>
+    <dimen name="usage_bytes_picker_edit_text_min_width">360dp</dimen>
+
+    <!-- Alert Dialog -->
+    <dimen name="alert_dialog_margin_top">@*android:dimen/car_padding_4</dimen>
+    <dimen name="alert_dialog_margin_bottom">@*android:dimen/car_padding_4</dimen>
+    <dimen name="alert_dialog_title_margin_bottom">@*android:dimen/car_padding_4</dimen>
+    <dimen name="alert_dialog_title_margin_start">@*android:dimen/car_padding_3</dimen>
+    <dimen name="alert_dialog_title_margin_end">@*android:dimen/car_padding_3</dimen>
+    <dimen name="alert_dialog_edit_text_margin_start">@*android:dimen/car_padding_3</dimen>
+    <dimen name="alert_dialog_edit_text_margin_end">@*android:dimen/car_padding_3</dimen>
+    <dimen name="alert_dialog_password_checkbox_margin_end">@*android:dimen/car_padding_3</dimen>
+    <dimen name="alert_dialog_password_checkbox_margin_start">@*android:dimen/car_padding_3</dimen>
+    <dimen name="alert_dialog_password_checkbox_margin_top">@*android:dimen/car_padding_1</dimen>
+</resources>
diff --git a/res/values/floats.xml b/res/values/floats.xml
index 5db06cb..cbc4001 100644
--- a/res/values/floats.xml
+++ b/res/values/floats.xml
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!--
+    Copyright 2018 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
+    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
+         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.
+    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.
 -->
 
-
 <resources>
-  <!-- opacity -->
-  <item name="opacity_disabled" format="float" type="dimen">0.5</item>
-  <item name="opacity_enabled" format="float" type="dimen">1</item>
+    <!-- opacity -->
+    <item name="opacity_disabled" format="float" type="dimen">0.5</item>
+    <item name="opacity_enabled" format="float" type="dimen">1</item>
 </resources>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index cbaa8f2..91c9522 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2018 The Android Open Source Project
+    Copyright 2018 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
+         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,
@@ -14,6 +14,7 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
+
 <resources>
     <!-- Weight of the main content in the illustration_layout -->
     <integer name="content_weight">7</integer>
@@ -29,4 +30,19 @@
 
     <!-- user switcher column number TODO: move to support library-->
     <integer name="user_switcher_num_col">3</integer>
-</resources>
\ No newline at end of file
+
+    <!-- Number of times to select build number before enabling developer settings -->
+    <integer name="enable_developer_settings_click_count">7</integer>
+
+    <!-- Number of times to select build number before toast appears -->
+    <integer name="enable_developer_settings_clicks_to_show_toast_count">3</integer>
+
+    <!-- Maximum ems value for all settings buttons -->
+    <integer name="button_max_ems">9</integer>
+
+    <!-- Ems value for data usage bytes threshold picker -->
+    <integer name="data_usage_bytes_threshold_ems">15</integer>
+
+    <!-- Maximum size in pixels of default app icons -->
+    <integer name="default_app_safe_icon_size">500</integer>
+</resources>
diff --git a/res/values/internal_resources.xml b/res/values/internal_resources.xml
new file mode 100644
index 0000000..73286d4
--- /dev/null
+++ b/res/values/internal_resources.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<resources>
+    <bool name="config_automatic_brightness_available">@*android:bool/config_automatic_brightness_available</bool>
+    <integer name="config_networkAvoidBadWifi">@*android:integer/config_networkAvoidBadWifi</integer>
+
+    <!-- Progress bar preference resources -->
+    <color name="config_progress_background_tint">@*android:color/config_progress_background_tint</color>
+    <color name="white_disabled_material">@*android:color/white_disabled_material</color>
+</resources>
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
new file mode 100644
index 0000000..cf33c99
--- /dev/null
+++ b/res/values/preference_keys.xml
@@ -0,0 +1,376 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<!--
+    Identifiers for preferences. Namespaced with "pk" (Preference Key) to avoid conflicts.
+    Key values should be unique.
+-->
+<resources>
+    <!-- Homepage -->
+    <string name="pk_suggestions" translatable="false">suggestions</string>
+    <string name="pk_display_settings_entry" translatable="false">display_settings_entry</string>
+    <string name="pk_sound_settings_entry" translatable="false">sound_settings_entry</string>
+    <string name="pk_network_and_internet_entry" translatable="false">network_and_internet_entry
+    </string>
+    <string name="pk_wireless_extra_settings" translatable="false">wireless_extra_settings</string>
+    <string name="pk_bluetooth_settings_entry" translatable="false">bluetooth_settings_entry
+    </string>
+    <string name="pk_location_settings_entry" translatable="false">location_settings_entry</string>
+    <string name="pk_apps_and_notifications_settings_entry" translatable="false">
+        apps_and_notifications_settings_entry
+    </string>
+    <string name="pk_date_time_settings_entry" translatable="false">date_time_settings_entry
+    </string>
+    <string name="pk_users_settings_entry" translatable="false">users_settings_entry</string>
+    <string name="pk_accounts_settings_entry" translatable="false">accounts_settings_entry</string>
+    <string name="pk_storage_settings_entry" translatable="false">storage_settings_entry</string>
+    <string name="pk_security_settings_entry" translatable="false">security_settings_entry</string>
+    <string name="pk_system_settings_entry" translatable="false">system_settings_entry</string>
+    <string name="pk_device_extra_settings" translatable="false">device_extra_settings</string>
+    <string name="pk_personal_extra_settings" translatable="false">personal_extra_settings</string>
+
+    <!-- Network -->
+    <string name="pk_mobile_network_settings_entry" translatable="false">
+        mobile_network_settings_entry
+    </string>
+    <string name="pk_network_and_internet_extra_settings" translatable="false">
+        network_and_internet_extra_settings
+    </string>
+    <string name="pk_mobile_data_toggle" translatable="false">mobile_data_toggle</string>
+    <string name="pk_data_usage_settings_entry" translatable="false">data_usage_settings_entry
+    </string>
+    <string name="pk_data_usage_summary" translatable="false">data_usage_summary</string>
+    <string name="pk_data_warning_and_limit" translatable="false">data_warning_and_limit</string>
+    <string name="pk_data_usage_cycle" translatable="false">data_usage_cycle</string>
+    <string name="pk_data_warning_group" translatable="false">data_warning_group</string>
+    <string name="pk_data_set_warning" translatable="false">data_set_warning</string>
+    <string name="pk_data_warning" translatable="false">data_warning</string>
+    <string name="pk_data_limit_group" translatable="false">data_limit_group</string>
+    <string name="pk_data_set_limit" translatable="false">data_set_limit</string>
+    <string name="pk_data_limit" translatable="false">data_limit</string>
+    <string name="pk_app_data_usage" translatable="false">app_data_usage</string>
+    <string name="pk_app_data_usage_detail" translatable="false">app_data_usage_detail</string>
+    <string name="pk_wifi_tether_settings_entry" translatable="false">wifi_tether_settings_entry
+    </string>
+    <string name="pk_wifi_tether_security" translatable="false">wifi_tether_security</string>
+    <string name="pk_wifi_tether_ap_band" translatable="false">wifi_tether_ap_band</string>
+    <string name="pk_wifi_tether_auto_off" translatable="false">wifi_tether_auto_off</string>
+    <string name="pk_wifi_tether_password" translatable="false">wifi_tether_password</string>
+    <string name="pk_wifi_tether_name" translatable="false">wifi_tether_name</string>
+
+    <!-- WIFI -->
+    <string name="pk_wifi_settings_entry" translatable="false">wifi_settings_entry</string>
+    <string name="pk_wifi_frequency" translatable="false">wifi_frequency</string>
+    <string name="pk_wifi_signal_strength" translatable="false">wifi_signal_strength</string>
+    <string name="pk_wifi_security" translatable="false">wifi_security</string>
+    <string name="pk_wifi_mac_address" translatable="false">wifi_mac_address</string>
+    <string name="pk_wifi_ip" translatable="false">wifi_ip</string>
+    <string name="pk_wifi_ipv6" translatable="false">wifi_ipv6</string>
+    <string name="pk_wifi_gateway" translatable="false">wifi_gateway</string>
+    <string name="pk_wifi_subnet_mask" translatable="false">wifi_subnet_mask</string>
+    <string name="pk_wifi_dns" translatable="false">wifi_dns</string>
+    <string name="pk_wifi_link_speed" translatable="false">wifi_link_speed</string>
+    <string name="pk_wifi_list" translatable="false">wifi_list</string>
+    <string name="pk_add_wifi" translatable="false">add_wifi</string>
+    <string name="pk_wifi_status" translatable="false">wifi_status</string>
+    <string name="pk_add_wifi_network_name" translatable="false">add_wifi_network_name</string>
+    <string name="pk_add_wifi_security" translatable="false">add_wifi_security</string>
+    <string name="pk_add_wifi_password" translatable="false">add_wifi_password</string>
+    <string name="pk_wifi_preferences" translatable="false">wifi_preferences</string>
+    <string name="pk_wifi_cellular_fallback" translatable="false">wifi_cellular_fallback</string>
+    <string name="pk_enable_wifi_wakeup" translatable="false">enable_wifi_wakeup</string>
+
+    <!-- Bluetooth -->
+    <string name="pk_bluetooth_paired_devices" translatable="false">bluetooth_paired_devicesd
+    </string>
+    <string name="pk_bluetooth_pair_new_device" translatable="false">bluetooth_pair_new_device
+    </string>
+    <string name="pk_bluetooth_name" translatable="false">bluetooth_name</string>
+    <string name="pk_bluetooth_address" translatable="false">bluetooth_address</string>
+    <string name="pk_bluetooth_available_devices" translatable="false">bluetooth_available_devices
+    </string>
+    <string name="pk_bluetooth_device_name" translatable="false">bluetooth_device_name</string>
+    <string name="pk_bluetooth_device_profiles" translatable="false">bluetooth_device_profiles
+    </string>
+    <string name="pk_bluetooth_device_address" translatable="false">bluetooth_device_address
+    </string>
+    <string name="pk_bluetooth_device_picker" translatable="false">bluetooth_device_picker</string>
+
+    <!-- Applications and Notifications Settings -->
+    <string name="pk_applications_settings_screen_entry" translatable="false">
+        applications_settings_screen_entry
+    </string>
+    <string name="pk_default_applications_settings_entry" translatable="false">
+        default_applications_entry
+    </string>
+    <string name="pk_app_permissions_entry" translatable="false">app_permissions_entry</string>
+    <string name="pk_special_access_entry" translatable="false">special_access_entry</string>
+
+    <!-- Applications Settings -->
+    <string name="pk_all_applications_settings_list" translatable="false">
+        all_applications_settings_list
+    </string>
+    <string name="pk_application_extra_settings" translatable="false">
+        application_extra_settings
+    </string>
+
+    <!-- Application Details -->
+    <string name="pk_application_details_app" translatable="false">application_details_app</string>
+    <string name="pk_application_details_notifications" translatable="false">
+        application_details_notifications
+    </string>
+    <string name="pk_application_details_permissions" translatable="false">
+        application_details_permissions
+    </string>
+    <string name="pk_application_details_version" translatable="false">
+        application_details_version
+    </string>
+
+    <!-- Default apps Settings -->
+    <string name="pk_default_assist_and_voice" translatable="false">default_assist_and_voice
+    </string>
+    <string name="pk_default_assist" translatable="false">default_assist</string>
+    <string name="pk_default_assist_and_voice_options" translatable="false">
+        default_assist_and_voice_options
+    </string>
+    <string name="pk_assist_use_text_context" translatable="false">assist_use_text_context</string>
+    <string name="pk_assist_use_screenshot" translatable="false">assist_use_screenshot</string>
+    <string name="pk_default_voice_input" translatable="false">default_voice_input</string>
+    <string name="pk_default_voice_input_options" translatable="false">default_voice_input_options
+    </string>
+    <string name="pk_default_autofill_options" translatable="false">default_autofill_options
+    </string>
+    <string name="pk_opening_links_entry" translatable="false">opening_links_entry</string>
+    <string name="pk_opening_links_options" translatable="false">opening_links_options</string>
+    <string name="pk_opening_links_app_details_entry" translatable="false">
+        opening_links_app_details_entry
+    </string>
+    <string name="pk_opening_links_app_details" translatable="false">opening_links_app_details
+    </string>
+    <string name="pk_opening_links_app_details_state" translatable="false">
+        opening_links_app_details_state
+    </string>
+    <string name="pk_opening_links_app_details_urls" translatable="false">
+        opening_links_app_details_urls
+    </string>
+    <string name="pk_opening_links_app_details_reset" translatable="false">
+        opening_links_app_details_reset
+    </string>
+
+    <!-- Special App Access Settings -->
+    <string name="pk_modify_system_settings_entry" translatable="false">
+        modify_system_settings_entry
+    </string>
+    <string name="pk_modify_system_settings" translatable="false">modify_system_settings</string>
+    <string name="pk_modify_system_settings_description" translatable="false">
+        modify_system_settings_description
+    </string>
+    <string name="pk_notification_access_entry" translatable="false">notification_access_entry
+    </string>
+    <string name="pk_notification_access" translatable="false">notification_access</string>
+    <string name="pk_premium_sms_access_entry" translatable="false">premium_sms_access_entry
+    </string>
+    <string name="pk_premium_sms_access" translatable="false">premium_sms_access</string>
+    <string name="pk_premium_sms_access_description" translatable="false">
+        premium_sms_access_description
+    </string>
+    <string name="pk_usage_access_entry" translatable="false">usage_access_entry</string>
+    <string name="pk_usage_access" translatable="false">usage_access</string>
+    <string name="pk_usage_access_description" translatable="false">usage_access_description
+    </string>
+    <string name="pk_directory_access_entry" translatable="false">directory_access_entry</string>
+    <string name="pk_directory_access" translatable="false">directory_access</string>
+    <string name="pk_directory_access_details" translatable="false">directory_access_details
+    </string>
+    <string name="pk_directory_access_details_app" translatable="false">
+        directory_access_details_app
+    </string>
+    <string name="pk_wifi_control_entry" translatable="false">wifi_control_entry</string>
+    <string name="pk_wifi_control" translatable="false">wifi_control</string>
+    <string name="pk_wifi_control_description" translatable="false">wifi_control_description
+    </string>
+
+    <!-- DateTime Settings -->
+    <string name="pk_auto_datetime_switch" translatable="false">auto_datetime_switch</string>
+    <string name="pk_auto_timezone_switch" translatable="false">auto_timezone_switch</string>
+    <string name="pk_date_picker_entry" translatable="false">date_picker_entry</string>
+    <string name="pk_time_picker_entry" translatable="false">time_picker_entry</string>
+    <string name="pk_timezone_picker_screen_entry" translatable="false">
+        timezone_picker_screen_entry
+    </string>
+    <string name="pk_use_24hour_switch" translatable="false">use_24hour_switch</string>
+    <string name="pk_timezone_picker_screen" translatable="false">timezone_picker_screen</string>
+
+    <!-- Accounts -->
+    <string name="pk_account_list" translatable="false">account_list</string>
+    <string name="pk_account_settings" translatable="false">account_settings</string>
+    <string name="pk_accounts_extra_settings" translatable="false">accounts_extra_settings</string>
+    <string name="pk_account_auto_sync" translatable="false">account_auto_sync</string>
+    <string name="pk_add_account" translatable="false">add_account</string>
+    <string name="pk_account_details" translatable="false">account_details</string>
+    <string name="pk_account_sync" translatable="false">account_sync</string>
+    <string name="pk_account_details_with_sync" translatable="false">account_details_with_sync
+    </string>
+    <string name="pk_account_sync_details" translatable="false">account_sync_details</string>
+
+    <!-- Storage -->
+    <string name="pk_storage_music_audio" translatable="false">storage_music_audio</string>
+    <string name="pk_storage_music_audio_details" translatable="false">storage_music_audio_details
+    </string>
+    <string name="pk_storage_other_apps" translatable="false">storage_other_apps</string>
+    <string name="pk_storage_other_apps_details" translatable="false">storage_other_apps_details
+    </string>
+    <string name="pk_storage_application_size" translatable="false">storage_application_size
+    </string>
+    <string name="pk_storage_application_total_size" translatable="false">
+        storage_application_total_size
+    </string>
+    <string name="pk_storage_application_data_size" translatable="false">
+        storage_application_data_size
+    </string>
+    <string name="pk_storage_application_cache_size" translatable="false">
+        storage_application_cache_size
+    </string>
+    <string name="pk_storage_application_details" translatable="false">storage_application_details
+    </string>
+    <string name="pk_storage_files" translatable="false">storage_files</string>
+    <string name="pk_storage_system" translatable="false">storage_system</string>
+
+    <!-- Display Settings -->
+    <string name="pk_adaptive_brightness_switch" translatable="false">adaptive_brightness_switch
+    </string>
+    <string name="pk_brightness_level" translatable="false">brightness_level</string>
+    <string name="pk_display_extra_settings" translatable="false">display_extra_settings</string>
+
+    <!-- Sound Settings -->
+    <string name="pk_volume_settings" translatable="false">volume_settings</string>
+    <string name="pk_default_ringtone" translatable="false">default_ringtone</string>
+    <string name="pk_default_notification" translatable="false">default_notification</string>
+    <string name="pk_default_alarm" translatable="false">default_alarm</string>
+    <string name="pk_sounds_extra_settings" translatable="false">sounds_extra_settings</string>
+
+    <!-- Location Settings -->
+    <string name="pk_location_recent_requests_entry" translatable="false">
+        location_recent_requests_entry
+    </string>
+    <string name="pk_location_recent_requests_screen" translatable="false">
+        location_recent_requests_screen
+    </string>
+    <string name="pk_location_app_permissions" translatable="false">location_app_permissions
+    </string>
+    <string name="pk_location_scanning" translatable="false">location_scanning</string>
+    <string name="pk_location_scanning_wifi" translatable="false">location_scanning_wifi</string>
+    <string name="pk_location_scanning_bluetooth" translatable="false">location_scanning_bluetooth
+    </string>
+    <string name="pk_location_services" translatable="false">location_services</string>
+    <string name="pk_location_footer" translatable="false">location_footer</string>
+
+    <!-- Users Settings -->
+    <string name="pk_users_list" translatable="false">users_list</string>
+    <string name="pk_choose_new_admin" translatable="false">choose_new_admin</string>
+    <string name="pk_edit_user_name_entry" translatable="false">edit_user_name_entry</string>
+    <string name="pk_make_user_admin" translatable="false">make_user_admin</string>
+    <string name="pk_user_permissions" translatable="false">user_permissions</string>
+
+    <!-- Security Settings -->
+    <string name="pk_no_lock" translatable="false">no_lock</string>
+    <string name="pk_pattern_lock" translatable="false">pattern_lock</string>
+    <string name="pk_password_lock" translatable="false">password_lock</string>
+    <string name="pk_pin_lock" translatable="false">pin_lock</string>
+
+    <!-- System Settings -->
+    <string name="pk_languages_and_input_settings" translatable="false">
+        languages_and_input_settings
+    </string>
+    <string name="pk_system_update_settings" translatable="false">system_update_settings</string>
+    <string name="pk_system_extra_settings" translatable="false">system_extra_settings</string>
+    <string name="pk_about_settings_entry" translatable="false">about_settings_entry</string>
+    <string name="pk_legal_information_entry" translatable="false">legal_information_entry</string>
+    <string name="pk_reset_options_entry" translatable="false">reset_options_entry</string>
+    <string name="pk_developer_options_entry" translatable="false">developer_options_entry</string>
+
+    <!-- Language Settings -->
+    <string name="pk_language_settings_entry" translatable="false">language_settings_entry</string>
+    <string name="pk_language_and_input_extra_settings" translatable="false">
+        pk_language_and_input_extra_settings
+    </string>
+    <string name="pk_current_languages" translatable="false">current_languages</string>
+    <string name="pk_add_language" translatable="false">add_language</string>
+    <string name="pk_language_picker" translatable="false">language_picker</string>
+    <string name="pk_child_locale_picker" translatable="false">child_locale_picker</string>
+    <string name="pk_autofill_picker_entry" translatable="false">autofill_picker_entry</string>
+
+    <!-- Keyboard Settings -->
+    <string name="pk_keyboard_entry" translatable="false">keyboard_entry</string>
+    <string name="pk_enabled_keyboards" translatable="false">enabled_keyboards</string>
+    <string name="pk_manage_keyboard" translatable="false">manage_keyboard</string>
+    <string name="pk_keyboard_management" translatable="false">keyboard_management</string>
+
+    <!-- TTS Settings -->
+    <string name="pk_tts_settings_entry" translatable="false">tts_settings_entry</string>
+    <string name="pk_tts_preferred_engine_entry" translatable="false">tts_preferred_engine_entry
+    </string>
+    <string name="pk_tts_preferred_engine_options" translatable="false">
+        tts_preferred_engine_options
+    </string>
+    <string name="pk_tts_playback_group" translatable="false">tts_playback_group</string>
+    <string name="pk_tts_default_language" translatable="false">tts_default_language</string>
+    <string name="pk_tts_speech_rate" translatable="false">tts_speech_rate</string>
+    <string name="pk_tts_pitch" translatable="false">tts_pitch</string>
+    <string name="pk_tts_reset" translatable="false">tts_reset</string>
+
+    <!-- Legal information -->
+    <string name="pk_system_license_entry" translatable="false">system_license_entry</string>
+    <string name="pk_third_party_license_entry" translatable="false">third_party_license_entry
+    </string>
+
+    <!-- About Settings -->
+    <string name="pk_model_info" translatable="false">model_info</string>
+    <string name="pk_firmware_version" translatable="false">firmware_version</string>
+    <string name="pk_security_patch" translatable="false">security_patch</string>
+    <string name="pk_kernel_version" translatable="false">kernel_version</string>
+    <string name="pk_build_number" translatable="false">build_number</string>
+    <string name="pk_regulatory_labels" translatable="false">regulatory_labels</string>
+    <string name="pk_about_bluetooth_mac_address" translatable="false">about_bluetooth_mac_address
+    </string>
+    <string name="pk_about_wifi_mac_address" translatable="false">about_wifi_mac_address</string>
+
+    <!-- Reset Options -->
+    <string name="pk_reset_network" translatable="false">reset_network</string>
+    <string name="pk_reset_app_pref" translatable="false">reset_app_pref</string>
+    <string name="pk_master_clear" translatable="false">master_clear</string>
+
+    <!-- Reset Network -->
+    <string name="pk_reset_network_items" translatable="false">reset_network_items</string>
+    <string name="pk_reset_esim" translatable="false">reset_esim</string>
+    <string name="pk_reset_network_subscription" translatable="false">reset_network_subscription
+    </string>
+
+    <!-- Master Clear -->
+    <string name="pk_master_clear_desc" translatable="false">master_clear_desc</string>
+    <string name="pk_master_clear_account_list" translatable="false">master_clear_account_list
+    </string>
+    <string name="pk_master_clear_other_users_present" translatable="false">
+        master_clear_other_users_present
+    </string>
+    <string name="pk_master_clear_reset_esim" translatable="false">master_clear_reset_esim</string>
+    <string name="pk_master_clear_confirm_desc" translatable="false">master_clear_confirm_desc
+    </string>
+
+    <!-- Developer Options -->
+    <string name="pk_usb_debugging_toggle" translatable="false">usb_debugging_toggle</string>
+</resources>
diff --git a/res/values/string_arrays.xml b/res/values/string_arrays.xml
index c933ee0..50028ad 100644
--- a/res/values/string_arrays.xml
+++ b/res/values/string_arrays.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+    Copyright 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 
 <resources>
     <string-array name="wifi_security_method">
@@ -21,4 +21,37 @@
         <item>@string/wifi_security_wep</item>
         <item>@string/wifi_security_wpa_wpa2</item>
     </string-array>
+
+    <!-- Security types for wireless tether -->
+    <string-array translatable="false" name="wifi_tether_security">
+        <item>@string/wifi_security_wpa2</item>
+        <item>@string/wifi_security_none</item>
+    </string-array>
+
+    <!-- Wi-Fi AP band settings.  Either Auto, 2.4GHz or 5GHz. -->
+    <!-- Note that adding/removing/moving the items will need wifi settings code change. -->
+    <string-array translatable="false" name="wifi_ap_band_config_full">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+
+    <string-array translatable="false" name="wifi_ap_band_summary_full">
+        <item>@string/wifi_ap_choose_2G</item>
+        <item>@string/wifi_ap_choose_5G</item>
+    </string-array>
+
+    <string-array translatable="false" name="wifi_ap_band_dual_mode">
+        <item>0</item>
+        <item>-1</item>
+    </string-array>
+
+    <string-array translatable="false" name="wifi_ap_band_dual_mode_summary">
+        <item>@string/wifi_ap_choose_2G</item>
+        <item>@string/wifi_ap_prefer_5G</item>
+    </string-array>
+
+    <string-array translatable="false" name="wifi_ap_band_config_2G_only">
+        <item>@string/wifi_ap_choose_auto</item>
+        <item>@string/wifi_ap_choose_2G</item>
+    </string-array>
 </resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7ca240e..3b38e1e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,23 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
- * Copyright (c) 2017, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-*/
+    Copyright 2018 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.
 -->
+
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Application name, temporarily misspelled to allow it to live alongside the regular
-         android settings without confusion. -->
+    <!-- Application name -->
     <string name="settings_label">Settings</string>
     <string name="more_settings_label">More</string>
     <string name="display_settings">Display</string>
@@ -36,39 +35,118 @@
     <!-- Label for night mode toggle tile in quick setting [CHAR LIMIT=20] -->
     <string name="night_mode_tile_label">Night mode</string>
 
-    <!-- wifi settings--><skip />
+    <!-- Network and internet settings [CHAR LIMIT=40] -->
+    <string name="network_and_internet">Network &amp; internet</string>
+    <!-- Mobile network settings [CHAR LIMIT=30] -->
+    <string name="mobile_network_settings">Mobile network</string>
+    <!-- Mobile network screen, toggle mobile data title [CHAR LIMIT=30] -->
+    <string name="mobile_network_toggle_title">Mobile data</string>
+    <!-- Mobile network screen, toggle mobile data summary [CHAR LIMIT=50] -->
+    <string name="mobile_network_toggle_summary">Access data using mobile network</string>
+    <!-- Mobile network screen, confirmation dialog to turn off mobile data [CHAR LIMIT=50] -->
+    <string name="confirm_mobile_data_disable">Turn off mobile data?</string>
+    <!-- Data usage settings [CHAR LIMIT=30] -->
+    <string name="data_usage_settings">Data usage</string>
+    <!-- Data usage title text [CHAR LIMIT=30] -->
+    <string name="data_usage_title">Primary data</string>
+    <!-- Data usage, amount used [CHAR LIMIT=30] -->
+    <string name="data_used_formatted"><xliff:g name="value" example="500">^1</xliff:g> <xliff:g name="units" example="GB">^2</xliff:g> used</string>
+    <!-- Format for a summary describing the amount of data before the user is warned [CHAR LIMIT=40] -->
+    <string name="cell_data_warning"><xliff:g name="amount" example="1 GB">^1</xliff:g> data warning</string>
+    <!-- Format for a summary describing the amount of data the limit is set to [CHAR LIMIT=40] -->
+    <string name="cell_data_limit"><xliff:g name="amount" example="1 GB">^1</xliff:g> data limit</string>
+    <!-- Format for a summary describing the amount of data before the user is warned or limited [CHAR LIMIT=80] -->
+    <string name="cell_data_warning_and_limit"><xliff:g name="amount" example="1 GB">^1</xliff:g> data warning / <xliff:g name="amount" example="2 GB">^2</xliff:g> data limit</string>
+    <!-- Informational text about time left in billing cycle [CHAR LIMIT=40] -->
+    <plurals name="billing_cycle_days_left">
+        <item quantity="one">%d day left</item>
+        <item quantity="other">%d days left</item>
+    </plurals>
+    <!-- Informational text about time left in billing cycle [CHAR LIMIT=40] -->
+    <string name="billing_cycle_none_left">No time remaining</string>
+    <!-- Informational text about time left in billing cycle [CHAR LIMIT=40] -->
+    <string name="billing_cycle_less_than_one_day_left">Less than 1 day left</string>
+    <!-- Informational text about carrier and update time [CHAR LIMIT=32] -->
+    <string name="carrier_and_update_text">Updated by <xliff:g name="carrier" example="Google Fi">^1</xliff:g> <xliff:g name="time" example="3m">^2</xliff:g> ago</string>
+    <!-- Informational text about update time only, without carrier. First argument intentionally skipped. [CHAR LIMIT=30] -->
+    <string name="no_carrier_update_text">Updated <xliff:g name="time" example="3m">^2</xliff:g> ago</string>
+    <!-- Informational text about a recent carrier and update time [CHAR LIMIT=34] -->
+    <string name="carrier_and_update_now_text">Updated by <xliff:g name="carrier" example="Google Fi">^1</xliff:g> just now</string>
+    <!-- Informational text about recent update time only, without carrier [CHAR LIMIT=30] -->
+    <string name="no_carrier_update_now_text">Updated just now</string>
+    <!-- Button to launch external data plan app [CHAR LIMIT=30] -->
+    <string name="launch_manage_plan_text">View plan</string>
+    <!-- Title for mobile data preference, to display the mobile data usage for each app. [CHAR LIMIT=30] -->
+    <string name="app_data_usage">App data usage</string>
+    <!-- Label for application which has its data usage restricted. [CHAR LIMIT=16] -->
+    <string name="data_usage_app_restricted">restricted</string>
+
+    <!-- Title of dialog for editing data usage cycle reset date. [CHAR LIMIT=48] -->
+    <string name="cycle_reset_day_of_month_picker_title">Usage cycle reset date</string>
+    <!-- Subtitle of dialog for editing data usage cycle reset date. [CHAR LIMIT=32] -->
+    <string name="cycle_reset_day_of_month_picker_subtitle">Date of each month:</string>
+    <!-- Positive button title for data usage cycle reset date picker, confirming that changes should be saved. [CHAR LIMIT=32] -->
+    <string name="cycle_reset_day_of_month_picker_positive_button">Set</string>
+    <!-- Title of button and screen for billing cycle preferences [CHAR LIMIT=40] -->
+    <string name="data_warning_limit_title">Data warning &amp; limit</string>
+    <!-- Title of button for application usage cycle preferences [CHAR LIMIT=40] -->
+    <string name="app_usage_cycle">App data usage cycle</string>
+    <!-- Label for switch about whether to warn user about usage [CHAR LIMIT=40] -->
+    <string name="set_data_warning">Set data warning</string>
+    <!-- Label for button to set the amount of data before user is warned about usage [CHAR LIMIT=30] -->
+    <string name="data_warning">Data warning</string>
+    <!-- Label for switch about whether to limit how much data can be used [CHAR LIMIT=30] -->
+    <string name="set_data_limit">Set data limit</string>
+    <!-- Label for button to set the amount of data before user is limited [CHAR LIMIT=30] -->
+    <string name="data_limit">Data limit</string>
+    <!-- Title of dialog shown before user limits data usage. [CHAR LIMIT=48] -->
+    <string name="data_usage_limit_dialog_title">Limiting data usage</string>
+    <!-- Body of dialog shown before user limits mobile data usage. [CHAR LIMIT=NONE] -->
+    <string name="data_usage_limit_dialog_mobile" >Your vehicle\u2019s head unit will turn off mobile data once it reaches the limit you set.\n\nSince data usage is measured by the head unit, and your carrier may account for usage differently, consider setting a conservative limit.</string>
+    <!-- Title of dialog for editing data usage warning in bytes. [CHAR LIMIT=48] -->
+    <string name="data_usage_warning_editor_title">Set data usage warning</string>
+    <!-- Title of dialog for editing data usage limit in bytes. [CHAR LIMIT=48] -->
+    <string name="data_usage_limit_editor_title">Set data usage limit</string>
+    <!-- Positive button title for data usage bytes threshold picker, confirming that changes should be saved. [CHAR LIMIT=32] -->
+    <string name="usage_bytes_threshold_picker_positive_button">Set</string>
+    <string-array name="bytes_picker_sizes" translatable="false">
+        <item>@*android:string/megabyteShort</item>
+        <item>@*android:string/gigabyteShort</item>
+    </string-array>
+
+    <!-- wifi settings--><skip/>
     <!-- Used in the 1st-level settings screen to go to the 2nd-level settings screen  [CHAR LIMIT=20]-->
     <string name="wifi_settings">Wi\u2011Fi</string>
-    <!-- Summary text of the Wi-fi settings screen -->
-    <string name="wifi_settings_summary">Set up &amp; manage wireless access points</string>
     <!-- Summary text when turning Wi-Fi or bluetooth on -->
     <string name="wifi_starting">Turning Wi\u2011Fi on\u2026</string>
     <!-- Summary text when turning Wi-Fi or bluetooth off -->
     <string name="wifi_stopping">Turning off Wi\u2011Fi\u2026</string>
+    <!-- Summary text when loading Wi-Fi list -->
+    <string name="loading_wifi_list">Loading Wi\u2011Fi list</string>
+    <!-- Summary text shown in place of the Wi-Fi list when wifi is off -->
+    <string name="wifi_disabled">Wi\u2011Fi disabled</string>
     <!-- Failured notification for forget -->
     <string name="wifi_failed_forget_message">Failed to forget network</string>
     <!-- Failured notification for connect -->
     <string name="wifi_failed_connect_message">Failed to connect to network</string>
-    <!-- Button message shown on the button adding manual setting.
-     Used in Wifi Setup For Setup Wizard with XL screen. -->
+    <!-- Button message shown on the button adding manual setting. -->
     <string name="wifi_setup_add_network">Add network</string>
-    <!-- Summary text when Wi-Fi is turned off -->
-    <string name="wifi_disabled">Wi\u2011Fi disabled</string>
-    <!-- Button label to connect to a Wi-Fi network.
-     Used in SetupWizard for XLarge screen [CHAR LIMIT=10] -->
+    <!-- Button label to connect to a Wi-Fi network. [CHAR LIMIT=10] -->
     <string name="wifi_setup_connect">Connect</string>
     <!-- Label for the password of the secured network -->
     <string name="wifi_password">Password</string>
     <!-- Label for the check box to show password -->
     <string name="wifi_show_password">Show password</string>
+    <!-- Toast message when trying to connect to a hidden wifi network without a network name. [CHAR LIMIT=NONE] -->
+    <string name="wifi_no_network_name">Please enter a network name</string>
     <!-- Label for the SSID of the network -->
     <string name="wifi_ssid">Network name</string>
     <!-- Hint for a text field to enter the SSID of a hidden wifi network. [CHAR LIMIT=35] -->
-    <!-- <string name="wifi_ssid_hint">Enter the SSID</string> -->
+    <string name="wifi_ssid_hint">Enter the SSID</string>
     <!-- Label for the security of the connection -->
     <string name="wifi_security">Security</string>
     <!-- Label for the signal strength of the connection -->
-    <string name="wifi_signal">Signal strength</string>
+    <string name="wifi_signal_strength">Signal strength</string>
     <!-- Label for the status of the connection -->
     <string name="wifi_status">Status</string>
     <!-- Label for the link speed of the connection -->
@@ -77,76 +155,144 @@
     <string name="wifi_frequency">Frequency</string>
     <!-- Label for the IP address of the connection -->
     <string name="wifi_ip_address">IP address</string>
+    <!-- Text for show password ListItem -->
+    <string name="show_password">Show password</string>
+    <!-- Summary for wifi network name. [CHAR LIMIT=40] -->
+    <string name="default_network_name_summary">Enter network name</string>
+    <!-- Summary for wifi password dialog. [CHAR LIMIT=40] -->
+    <string name="default_password_summary">Enter password</string>
     <!-- Key for the tag to store UI info for access point -->
     <string name="access_point_tag_key">access_point_tag_key</string>
-    <!-- Match this with drawable.wifi_signal. --> <skip />
+    <!-- Match this with drawable.wifi_signal. --><skip/>
     <!-- Wi-Fi settings. The signal strength a Wi-Fi network has. -->
     <string-array name="wifi_signals">
         <item>Poor</item>
+        <item>Poor</item>
         <item>Fair</item>
         <item>Good</item>
         <item>Excellent</item>
     </string-array>
+    <!-- Link speed on Wifi Status screen -->
+    <string name="link_speed">%1$d Mbps</string>
+    <!-- Wifi 2.4GHz is used as an universal itendifier for 2.4GHz band -->
+    <string name="wifi_band_24ghz">2.4 GHz</string>
+    <!-- Wifi Internal 5GHz as an universal itendifier for 5GHz band -->
+    <string name="wifi_band_5ghz">5 GHz</string>
+    <!-- Label to the section to show details about a Wi-Fi settings -->
+    <string name="wifi_network_detail">Network details</string>
+    <!-- Wi-Fi settings screen, advanced, title of the item to show the Wi-Fi device's MAC address. -->
+    <string name="wifi_mac_address">MAC address</string>
+    <!-- Wi-Fi settings screen, advanced, title of the item to show the Wi-Fi device's current IP address. -->
+    <string name="wifi_ip_address_title">IP address</string>
+    <!-- Wifi details preference title to display router IP subnet mask -->
+    <string name="wifi_subnet_mask">Subnet mask</string>
+    <!-- Wifi details preference title to display router DNS info -->
+    <string name="wifi_dns">DNS</string>
+    <!-- Wifi details preference category title for IPv6 information -->
+    <string name="wifi_details_ipv6_address_header">IPv6 addresses</string>
+    <!-- Label for the gateway of the network -->
+    <string name="wifi_gateway">Gateway</string>
+    <!-- Title of wifi specific settings, which control advanced behaviors of wifi. [CHAR LIMIT=30] -->
+    <string name="wifi_preferences_title">Wi\u2011Fi preferences</string>
+    <!-- Preference title for option to enable Wi-Fi when saved networks are nearby [CHAR LIMIT=40] -->
+    <string name="wifi_wakeup">Turn on Wi\u2011Fi automatically</string>
+    <!-- Preference summary for option to enable Wi-Fi when high quality saved networks are nearby [CHAR LIMIT=120] -->
+    <string name="wifi_wakeup_summary">Wi\u2011Fi will turn back on near high\u2011quality saved networks, like your home network</string>
+    <!-- Preference summary for auto-wifi when user needs to enable location scanning to toggle it [CHAR LIMIT=100] -->
+    <string name="wifi_wakeup_summary_no_location">Unavailable because location is turned off. Turn on <annotation id="link">location</annotation>.</string>
+    <!-- Wi-Fi settings dialog. Title of dialog displayed asking user to enable Wi-Fi Scanning [CHAR LIMIT=60]-->
+    <string name="wifi_settings_scanning_required_title">Turn on Wi\u2011Fi scanning?</string>
+    <!-- Wi-Fi settings dialog. Text for the confirmation button to enable Wi-Fi scanning. [CHAR LIMIT = 20]-->
+    <string name="wifi_settings_scanning_required_turn_on">Turn on</string>
+    <!-- Wi-Fi settings dialog. Text to show in toast for when user turns on wifi scanning. [CHAR LIMIT=NONE] -->
+    <string name="wifi_settings_scanning_required_enabled">Wi\u2011Fi scanning turned on</string>
+    <!-- Preference title for option to automatically switch away from bad wifi networks [CHAR LIMIT=60]-->
+    <string name="wifi_cellular_fallback_title">Switch to mobile data automatically</string>
+    <!-- Preference summary to automatically switch away from bad wifi networks [CHAR LIMIT=None]-->
+    <string name="wifi_cellular_fallback_summary">Use mobile data when Wi\u2011Fi has no internet access. Data usage charges may apply.</string>
+    <!-- url for the wifi scanning required dialog help page -->
+    <string name="help_uri_wifi_scanning_required" translatable="false"></string>
+    <!-- Button label to allow the user to view additional information [CHAR LIMIT=NONE] -->
+    <string name="learn_more">Learn more</string>
+    <!-- Wifi hotspot settings -->
+    <!-- Label for Wifi hotspot name. [CHAR LIMIT=20]-->
+    <string name="wifi_hotspot_name_title">Hotspot name</string>
+    <!-- Summary for Wifi hotspot name when connection is in progress. [CHAR LIMIT=25]-->
+    <string name="wifi_hotspot_name_summary_connecting">Turning on <xliff:g id="wifi_hotspot_name">%1$s</xliff:g>...</string>
+    <!-- Summary for Wifi hotspot name when connected. [CHAR LIMIT=NONE]-->
+    <string name="wifi_hotspot_name_summary_connected">Other devices can connect to <xliff:g id="wifi_hotspot_name">%1$s</xliff:g></string>
+    <!-- Label for Wifi hotspot password. [CHAR LIMIT=30]-->
+    <string name="wifi_hotspot_password_title">Hotspot password</string>
+    <!-- Label for Wifi security type title [CHAR LIMIT=15]-->
+    <string name="wifi_hotspot_security_title">Security</string>
+    <!-- Label for Wifi security type WPA2-Personal [CHAR LIMIT=25]-->
+    <string name="wifi_hotspot_wpa2_personal">WPA2-Personal</string>
+    <!-- Label for Wifi security type WPA2-Personal [CHAR LIMIT=25]-->
+    <string name="wifi_hotspot_security_none">None</string>
+    <!-- Label for Wifi hotspot AP Band. [CHAR LIMIT=15]-->
+    <string name="wifi_hotspot_ap_band_title">AP Band</string>
+    <!-- Label for the RadioGroup to choose wifi ap band [CHAR LIMIT=25]-->
+    <string name="wifi_ap_band_config">Select AP Band</string>
+    <!-- Label for the radio button to choose wifi ap channel automatically [CHAR LIMIT=10]-->
+    <string name="wifi_ap_choose_auto">Auto</string>
+    <!-- Label for the radio button to choose wifi ap 2.4 GHz band [CHAR LIMIT=25]-->
+    <string name="wifi_ap_choose_2G">2.4 GHz Band</string>
+    <!-- Label for the radio button to only choose wifi ap 5GHz band [CHAR LIMIT=25]-->
+    <string name="wifi_ap_choose_5G">5.0 GHz Band</string>
+    <!-- Label for the radio button to prefer 5GHz wifi ap band  [CHAR LIMIT=80]-->
+    <string name="wifi_ap_prefer_5G">5.0 GHz Band preferred</string>
+    <!-- Label for adding to the list of selected bands when 2.4 GHz is selected [CHAR LIMIT=15]-->
+    <string name="wifi_ap_2G">2.4 GHz</string>
+    <!-- Label for adding to the list of selected bands when 5.0 GHz is selected [CHAR LIMIT=15]-->
+    <string name="wifi_ap_5G">5.0 GHz</string>
+    <!-- Label that is shown as a dialog summary informing users they must pick at LEAST one band for their hotspot [CHAR LIMIT=25]-->
+    <string name="wifi_ap_band_select_one">Choose at least one band for Wi\u2011Fi hotspot:</string>
+    <!-- Tethering controls, item title to go into the tethering settings when USB, Bluetooth and Wifi tethering are available [CHAR LIMIT=60]-->
+    <string name="tether_settings_title_all">Hotspot &amp; tethering</string>
+    <!-- Title for the toggle to turn off hotspot automatically [CHAR LIMIT=50]-->
+    <string name="wifi_hotspot_auto_off_title">Turn off hotspot automatically</string>
+    <!-- Summary for the toggle to turn off hotspot automatically [CHAR LIMIT=100]-->
+    <string name="wifi_hotspot_auto_off_summary">Wi\u2011Fi hotspot will turn off if no devices are connected</string>
 
-    <!-- Bluetooth settings --><skip />
-    <!-- Bluetooth settings check box title on Main Settings screen -->
-    <string name="bluetooth_quick_toggle_title">Bluetooth</string>
-    <!-- Bluetooth settings check box summary for turning on bluetooth -->
-    <string name="bluetooth_quick_toggle_summary">Turn on Bluetooth</string>
-    <!--Used as title on second screen after selecting Bluetooth settings -->
-    <string name="bluetooth_settings">Bluetooth</string>
-    <!--Bluetooth was disabled. -->
-    <string name="bluetooth_disabled">Bluetooth disabled</string>
-    <!--Wireless controls screen, settings title for the item to take you to the bluetooth settings screen -->
+    <!-- Bluetooth settings --><skip/>
+    <!-- Title of Bluetooth settings pages. [CHAR LIMIT=30] -->
     <string name="bluetooth_settings_title">Bluetooth</string>
-    <!--Wireless controls screen, settings summary for the item tot ake you to the bluetooth settings screen -->
-    <string name="bluetooth_settings_summary">Manage connections, set device name &amp; discoverability</string>
+    <!--Bluetooth settings screen, text for Bluetooth device with no name. [CHAR LIMIT=40] -->
+    <string name="bluetooth_device">Unnamed device</string>
+    <!-- Category title for paired Bluetooth devices. [CHAR LIMIT=40] -->
+    <string name="bluetooth_paired_devices">Paired devices</string>
+    <!-- Title for option to pair a new Bluetooth device. [CHAR LIMIT=40] -->
+    <string name="bluetooth_pair_new_device">Pair new device</string>
+    <!-- Summary for option to pair a new Bluetooth device; alerts user that selecting the option enables Bluetooth. [CHAR LIMIT=NONE]-->
+    <string name="bluetooth_pair_new_device_summary">Bluetooth will turn on to pair</string>
+    <!-- Dialog title to confirm disconnecting from all profiles of a Bluetooth device. [CHAR LIMIT=40] -->
+    <string name="bluetooth_disconnect_title">Disconnect device?</string>
+    <!-- Message for disconnecting a vehicle from all profiles of a Bluetooth device. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_disconnect_all_profiles">Your vehicle will disconnect from "<xliff:g example="Pixel" id="device_name">%1$s</xliff:g>."</string>
+    <!-- Description of a vehicle's Bluetooth MAC address. [CHAR LIMIT=50] -->
+    <string name="bluetooth_vehicle_mac_address">Vehicle\'s Bluetooth address: <xliff:g example="12:34:56:AB:CD:EF" id="address">%1$s</xliff:g></string>
+    <!-- Description of a remote device's Bluetooth MAC address. [CHAR LIMIT=50] -->
+    <string name="bluetooth_device_mac_address">Device\'s Bluetooth address: <xliff:g example="12:34:56:AB:CD:EF" id="address">%1$s</xliff:g></string>
+    <!-- Title for the vehicle's current Bluetooth name, e.g. "My Car". [CHAR LIMIT=30] -->
+    <string name="bluetooth_name">Vehicle name</string>
+    <!-- Dialog title to change the vehicle's Bluetooth name. [CHAR LIMIT=30] -->
+    <string name="bluetooth_rename_vehicle">Rename this vehicle</string>
+    <!-- Dialog title to change a remote device's Bluetooth name. [CHAR LIMIT=30] -->
+    <string name="bluetooth_rename_device">Rename device</string>
+    <!-- Confirmation button in dialog to rename a Bluetooth device. [CHAR LIMIT=20] -->
+    <string name="bluetooth_rename_button">Rename</string>
+    <!-- Category title for Bluetooth devices available for pairing. [CHAR LIMIT=40] -->
+    <string name="bluetooth_available_devices">Available devices</string>
+    <!-- Category title for Bluetooth profiles. [CHAR LIMIT=40] -->
+    <string name="bluetooth_profiles">Profiles</string>
+    <!-- Title for BT error dialogs. -->
+    <string name="bluetooth_error_title"></string>
 
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_computer">Computer</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_headset">Headset</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_phone">Phone</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_imaging">Imaging</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_headphone">Headphone</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_input_peripheral">Input Peripheral</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_bluetooth">Bluetooth</string>
-    <!-- Bluetooth settings: The sub heading for devices which have already been paired with this device. [CHAR LIMIT=40] -->
-    <string name="bluetooth_preference_paired_devices">Paired devices</string>
-    <!-- Bluetooth settings: The sub heading for available devices during and after scanning. [CHAR LIMIT=40] -->
-    <string name="bluetooth_preference_found_devices">Available devices</string>
-    <!-- Bluetooth settings: The sub heading for no bluetooth device has been paired with this device. [CHAR LIMIT=40] -->
-    <string name="bluetooth_preference_no_paired_devices">No paired devices</string>
-    <!-- Bluetooth settings: The sub heading for no available bluetooth devices during and after scanning. [CHAR LIMIT=40] -->
-    <string name="bluetooth_preference_no_found_devices">No available devices</string>
-    <!-- Bluetooth settings: Paired dialog title [CHAR LIMIT=40] -->
-    <string name="bluetooth_preference_paired_dialog_title">Paired device</string>
-    <!-- Bluetooth settings: Name label [CHAR LIMIT=40] -->
-    <string name="bluetooth_preference_paired_dialog_name_label">Name</string>
-    <!-- Bluetooth settings.  Connection options screen.  The title of the header that is above all of the profiles.
-     When a user decides what Bluetooth capabilities to use with the device.  -->
-    <string name="bluetooth_device_advanced_profile_header_title">Use for</string>
-    <!-- Hint for a text field to change the name of the Bluetooth device. [CHAR LIMIT=35] -->
-    <string name="wifi_ssid_hint">Change the name of the Bluetooth device</string>
-
-    <!-- Bluetooth pairing -->
+    <!-- Bluetooth pairing --><skip/>
     <!-- Notification ticker text (shown in the status bar) when a Bluetooth device wants to pair with us -->
     <string name="bluetooth_notif_ticker">Bluetooth pairing request</string>
     <!-- Bluetooth settings.  Context menu item for a device.  Action will first pair, and then connect to all profiles on the device. -->
     <string name="bluetooth_device_context_pair_connect">Pair &amp; connect</string>
-    <!-- Used as setting title (for checkbox) on second screen after selecting Bluetooth settings -->
-    <string name="bluetooth">Bluetooth</string>
     <!-- Message when bluetooth is informing the user of the pairing key. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_pairing_key_msg">Bluetooth pairing code</string>
     <!-- Checkbox label for alphanumeric PIN entry (default is numeric PIN). [CHAR LIMIT=50] -->
@@ -154,9 +300,9 @@
     <!-- Message when bluetooth dialog for passkey entry is showing. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_enter_passkey_msg">Type the pairing code then press Return or Enter</string>
     <!-- Title for the dialog to enter PIN. [CHAR LIMIT=40] -->
-    <string name="bluetooth_pairing_request">Pair with <xliff:g id="device_name">%1$s</xliff:g>?</string>
+    <string name="bluetooth_pairing_request">Pair with <xliff:g example="Pixel" id="device_name">%1$s</xliff:g>?</string>
     <!-- Checkbox message in pairing dialogs.  [CHAR LIMIT=NONE] -->
-    <string name="bluetooth_pairing_shares_phonebook">Allow <xliff:g id="device_name">%1$s</xliff:g> to access your contacts and call history</string>
+    <string name="bluetooth_pairing_shares_phonebook">Allow <xliff:g example="Pixel" id="device_name">%1$s</xliff:g> to access your contacts and call history</string>
     <!-- Pairing dialog text to remind user to enter the PIN on the other device. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_enter_pin_other_device">You may also need to type this PIN on the other device.</string>
     <!-- Pairing dialog text to remind user to enter the passkey on the other device. [CHAR LIMIT=NONE] -->
@@ -169,13 +315,31 @@
     <string name="bluetooth_notif_title">Pairing request</string>
     <!-- Notification message when a Bluetooth device wants to pair with us -->
     <string name="bluetooth_notif_message">Tap to pair with <xliff:g id="device_name">%1$s</xliff:g>.</string>
-    <!-- Title for BT error dialogs. -->
-    <string name="bluetooth_error_title"></string>
+    <!-- Title for page which supports selecting a Bluetooth device from other applications. [CHAR_LIMIT=40]-->
+    <string name="bluetooth_device_picker">Choose Bluetooth device</string>
 
     <!-- Language settings screen heading. [CHAR LIMIT=30] -->
     <string name="language_settings">Languages</string>
+    <!-- Title of Languages & input settings screen. [CHAR LIMIT=30] -->
+    <string name="languages_and_input_settings">Languages &amp; input</string>
+    <!-- Title for the 'Keyboard' preference sub-screen. [CHAR LIMIT=30] -->
+    <string name="keyboard_settings">Keyboard</string>
+    <!-- Title for the 'Available keyboards' preference sub-screen, where the user can turn on/off installed keyboards.[CHAR LIMIT=35] -->
+    <string name="manage_keyboard">Manage keyboards</string>
+    <!-- Title of the main TTS settings screen. [CHAR LIMIT=50] -->
+    <string name="text_to_speech_settings">Text-to-speech output</string>
+    <!-- Title of preferred TTS settings screen. [CHAR LIMIT=30] -->
+    <string name="text_to_speech_preferred_engine_settings">Preferred engine</string>
+    <!-- Summay which identifies the currently selected preferred engine. [CHAR LIMIT=30] -->
+    <string name="text_to_speech_current_engine">Current engine</string>
+    <!-- Title for the seek bar which controls the TTS speech rate. [CHAR LIMIT=40] -->
+    <string name="tts_speech_rate">Speech Rate</string>
+    <!-- Title for the seek bar which controls the TTS pitch. [CHAR LIMIT=30] -->
+    <string name="tts_pitch">Pitch</string>
+    <!-- Name for button that resets speech rate and pitch for synthesized voice to default values in the text to speech settings. [CHAR LIMIT=30] -->
+    <string name="tts_reset">Reset</string>
 
-    <!-- sound settings --><skip />
+    <!-- sound settings --><skip/>
     <!-- Sound settings screen heading -->
     <string name="sound_settings">Sound</string>
     <!-- Sound settings screen, setting option name -->
@@ -192,16 +356,48 @@
     <string name="media_volume_summary">Set volume for music and videos</string>
     <!-- Sound settings screen, alarm volume slider title -->
     <string name="alarm_volume_title">Alarm</string>
+    <!-- Sound settings screen, title for the option defining the phone ringtone. [CHAR LIMIT=30] -->
+    <string name="ringtone_title">Phone ringtone</string>
+    <!-- Sound settings screen, title for the option defining the default notification sound. [CHAR LIMIT=40] -->
+    <string name="notification_ringtone_title">Default notification sound</string>
+    <!-- Sound settings screen, title for the option defining the default alarm sound. [CHAR LIMIT=30] -->
+    <string name="alarm_ringtone_title">Default alarm sound</string>
 
-    <!-- applications --><skip />
-    <!-- Applications settings title, on main settings screen. If clicked, the user is taken to a settings screen full of application settings-->
+    <!-- applications --><skip/>
+    <!-- Apps and notifications settings title, on main settings screen. If clicked, the user is taken to a settings screen full of apps and notifications settings [CHAR LIMIT=40] -->
+    <string name="apps_and_notifications_settings">Apps &amp; notifications</string>
+    <!-- Preference which opens application settings when clicked [CHAR LIMIT=40] -->
+    <string name="all_applications">Show all apps</string>
+    <!-- Default app settings title, which allows user to select defaults for certain application types. [CHAR LIMIT=40] -->
+    <string name="default_applications">Default apps</string>
+    <!-- Label for list that shows all permissions [CHAR LIMIT=40] -->
+    <string name="app_permissions">App permissions</string>
+    <!-- Summary of permissions currently granted to apps [CHAR LIMIT=60] -->
+    <string name="app_permissions_summary">Apps using <xliff:g id="apps" example="location">%1$s</xliff:g></string>
+    <!-- Applications settings title. If clicked, the user is taken to the list of installed applications. [CHAR LIMIT=40] -->
     <string name="applications_settings">App info</string>
-    <!-- [CHAR LIMIT=25] Manage applications, individual application info screen, button label under Storage heading. Button to disable an existing application. -->
+    <!-- Manage applications, text label for button to kill / force stop an application. [CHAR LIMIT=30] -->
+    <string name="force_stop">Force stop</string>
+    <!-- Manage applications, title for dialog when killing persistent apps. [CHAR LIMIT=40] -->
+    <string name="force_stop_dialog_title">Force stop?</string>
+    <!-- Manage applications, text for dialog when killing persistent apps. [CHAR LIMIT=200] -->
+    <string name="force_stop_dialog_text">If you force stop an app, it may misbehave.</string>
+    <!-- Manage applications, individual application info screen. Button to disable an existing application. [CHAR LIMIT=25] -->
     <string name="disable_text">Disable</string>
-    <!-- [CHAR LIMIT=25] Manage applications, individual application info screen, button label under Storage heading. Button to re-enable an existing application. -->
+    <!-- Manage applications, individual application info screen. Button to re-enable an existing application. [CHAR LIMIT=25] -->
     <string name="enable_text">Enable</string>
+    <!-- Manage applications, individual application info screen. Button to remove the application from the system. [CHAR LIMIT=25] -->
+    <string name="uninstall_text">Uninstall</string>
+    <!-- Manage applications, text for dialog when disabling apps. [CHAR LIMIT=200] -->
+    <string name="app_disable_dialog_text">If you disable this app, Android and other apps may no longer function as intended.</string>
+    <!-- Manage applications, label for option to disable app. [CHAR LIMIT=30] -->
+    <string name="app_disable_dialog_positive">Disable app</string>
+    <!-- Manage applications, text informing that an application is not installed for the current user. [CHAR_LIMIT=NONE] -->
+    <string name="not_installed">Not installed for this user</string>
     <!-- Manage applications, individual application info screen, heading for settings related to the app's permissions. for example, it may list all the permissions the app has. -->
     <string name="permissions_label">Permissions</string>
+    <!-- Label for the toggle that enables/disables an app's notifications. [CHAR LIMIT=20] -->
+    <string name="notifications_label">Notifications</string>
     <!-- Label for displaying application version. -->
     <string name="application_version_label">Version: %1$s</string>
     <!-- Runtime permissions preference summary, shown when the app has no permissions granted. [CHAR LIMIT=40] -->
@@ -212,23 +408,150 @@
     <string name="data_usage_summary_title">Data usage</string>
     <!-- Activity title for Appk data usage summary. [CHAR LIMIT=25] -->
     <string name="data_usage_app_summary_title">App data usage</string>
-    <!-- Manage applications, text label for button to kill / force stop an application -->
-    <string name="force_stop">Force stop</string>
     <string name="computing_size">Computing\u2026</string>
     <!-- Runtime permissions preference summary.  Number of additional permissions granted. [CHAR LIMIT=40] -->
     <plurals name="runtime_permissions_additional_count">
-        <item quantity="one"><xliff:g id="count" example="1">%d</xliff:g> additional permission</item>
-        <item quantity="other"><xliff:g id="count" example="10">%d</xliff:g> additional permissions</item>
+        <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g> additional permission</item>
+        <item quantity="other"><xliff:g example="10" id="count">%d</xliff:g> additional permissions</item>
     </plurals>
+    <!-- Dialog body explaining that the app just selected by the user will not work after a reboot until after the user enters their credentials, such as a PIN or password. [CHAR LIMIT=NONE] -->
+    <string name="direct_boot_unaware_dialog_message_car">Note: After a reboot, this app can\'t start until you unlock your vehicle.</string>
+    <!-- Assistant and voice input settings title. This setting goes into the manage assistant settings page. [CHAR LIMIT=40] -->
+    <string name="assist_and_voice_input_settings">Assist &amp; voice input</string>
+    <!-- Default assistant settings title. This setting allows users to select the default assistant for the device. [CHAR LIMIT=40] -->
+    <string name="assist_app_settings">Assist app</string>
+    <!-- Title for the text context preference to determine whether assist can access the data currently displayed on-screen [CHAR LIMIT=40] -->
+    <string name="assist_access_context_title">Use text from screen</string>
+    <!-- Summary for the text context preference to determine whether assist can access the data currently displayed on-screen [CHAR LIMIT=NONE] -->
+    <string name="assist_access_context_summary">Allow the assist app to access the screen contents as text</string>
+    <!-- Title for the screenshot context preference to determine whether assist can access the screenshot of your screen [CHAR LIMIT=40] -->
+    <string name="assist_access_screenshot_title">Use screenshot</string>
+    <!-- Summary for the screenshot context preference to determine whether assist can access the screenshot of your screen [CHAR LIMIT=NONE] -->
+    <string name="assist_access_screenshot_summary">Allow the assist app to access an image of the screen</string>
+    <!-- Title of the screen of the voice input settings [CHAR_LIMIT=40]-->
+    <string name="voice_input_settings_title">Voice input</string>
+    <!-- Title of the autofill service settings screen. [CHAR LIMIT=40] -->
+    <string name="autofill_settings_title">Autofill service</string>
+    <!-- Title of the option to remove the default application. [CHAR LIMIT=30] -->
+    <string name="app_list_preference_none">None</string>
+    <!-- Label to show which application is chosen as the default application. [CHAR LIMIT=30] -->
+    <string name="default_app_selected_app">Selected</string>
+    <!-- Warning message about security implications of setting an assistant, displayed as a dialog message when the user selects an assistant. [CHAR_LIMIT=NONE]  -->
+    <string name="assistant_security_warning">The assistant will be able to read information about apps in use on your system, including information visible on your screen or accessible within the apps.</string>
+    <!-- Message of the warning dialog for setting the auto-fill app. [CHAR_LIMIT=NONE] -->
+    <string name="autofill_confirmation_message">
+        <![CDATA[
+        <b>Make sure you trust this app</b>
+        <br/>
+        <br/>
+        <xliff:g id="app_name" example="Google Autofill">%1$s</xliff:g> uses what\'s on
+        your screen to determine what can be autofilled.
+        ]]>
+    </string>
+    <!-- Title of preference to add new autofill services. [CHAR_LIMIT=30] -->
+    <string name="autofill_add_service">Add service</string>
+    <!-- Title of setting to change the handled domain urls [CHAR LIMIT=60]-->
+    <string name="app_launch_domain_links_title">Opening links</string>
+    <!-- Section title for the Domain URL app preference list [CHAR LIMIT=60]-->
+    <string name="domain_url_section_title">Installed apps</string>
+    <!-- Summary for an app that doesn't open any domain URLs [CHAR LIMIT=45] -->
+    <string name="domain_urls_summary_none">Don\u2019t open supported links</string>
+    <!-- Summary of an app that can open URLs for exactly one domain [CHAR LIMIT=45] -->
+    <string name="domain_urls_summary_one">Open <xliff:g id="domain" example="mail.google.com">%s</xliff:g></string>
+    <!-- Summary of an app that can open several domain's URLs [CHAR LIMIT=45] -->
+    <string name="domain_urls_summary_some">Open <xliff:g id="domain" example="mail.google.com">%s</xliff:g> and other URLs</string>
+    <!-- Title of app specific setting to view and modify app launch settings. [CHAR LIMIT=40] -->
+    <string name="app_launch_title">Open by default</string>
+    <!-- Sub heading for preferences not related to managed domain urls. [CHAR LIMIT=40] -->
+    <string name="app_launch_other_defaults_title">Other defaults</string>
+    <!-- Text if the app is NOT a default for actions. [CHAR LIMIT=40] -->
+    <string name="auto_launch_disable_text">No defaults set.</string>
+    <!-- Text if the app is set as a default for some actions. [CHAR LIMIT=80] -->
+    <string name="auto_launch_enable_text">You\u2019ve chosen to launch this app by default for some actions.</string>
+    <!-- Summary to indicate to user that the preference can be clicked to clear the default app preference. [CHAR LIMIT=40] -->
+    <string name="auto_launch_reset_text">Clear defaults</string>
+    <!-- Title of preference to pick how the app should open related urls [CHAR LIMIT=60] -->
+    <string name="app_launch_open_domain_urls_title">Open supported links</string>
+    <!-- Explanation that the app that will ALWAYS be launched to open web links to domains that it understands [CHAR LIMIT=40] -->
+    <string name="app_link_open_always">Open in this app</string>
+    <!-- Explanation that the user will be asked whether to launch the app to open web links to domains that it understands [CHAR LIMIT=40] -->
+    <string name="app_link_open_ask">Ask every time</string>
+    <!-- Explanation that the app that will NEVER be launched to open web links to domains that it understands [CHAR LIMIT=60] -->
+    <string name="app_link_open_never">Don\u2019t open in this app</string>
+    <!-- Title of dialog showing the supported link domains [CHAR LIMIT=40] -->
+    <string name="app_launch_supported_domain_urls_title">Supported links</string>
 
+    <!-- Label for screen where user can grant applications special access to various systems. [CHAR_LIMIT=60] -->
+    <string name="special_access">Special app access</string>
+    <!-- Text for toggle button to control whether system processes are shown in app lists. [CHAR_LIMIT=30] -->
+    <string name="show_system">Show system</string>
+    <!-- Text for toggle button to control whether system processes are hidden in app lists. [CHAR_LIMIT=30] -->
+    <string name="hide_system">Hide system</string>
+    <!-- Title for managing apps which can modify system settings. [CHAR_LIMIT=40] -->
+    <string name="modify_system_settings_title">Modify system settings</string>
+    <!-- Description of modifying system settings. [CHAR_LIMIT=NONE] -->
+    <string name="modify_system_settings_description">This permission allows an app to modify system settings.</string>
+    <!-- Title for managing components which can listen to notifications. [CHAR_LIMIT=30] -->
+    <string name="notification_access_title">Notification access</string>
+    <!-- Title for a warning message about security implications of enabling a notification listener, displayed as a dialog message. [CHAR_LIMIT=NONE] -->
+    <string name="notification_listener_security_warning_title">Allow notification access for <xliff:g id="service" example="NotificationReader">%1$s</xliff:g>?</string>
+    <!-- Summary for a warning message about security implications of enabling a notification listener, displayed as a dialog message. [CHAR_LIMIT=NONE] -->
+    <string name="notification_listener_security_warning_summary"><xliff:g id="notification_listener_name">%1$s</xliff:g> will be able to read all notifications, including personal information such as contact names and the text of messages you receive. It will also be able to dismiss notifications or trigger action buttons they contain.\n\nThis will also give the app the ability to turn Do Not Disturb on or off and change related settings.</string>
+    <!-- Summary for a warning message about revoking notification listener access, displayed as a dialog message. [CHAR_LIMIT=NONE] -->
+    <string name="notification_listener_revoke_warning_summary">If you turn off notification access for <xliff:g id="notification_listener_name">%1$s</xliff:g>, Do Not Disturb access may also be turned off.</string>
+    <!-- Positive button for a dialog warning message about revoking notification listener access. [CHAR_LIMIT=30] -->
+    <string name="notification_listener_revoke_warning_confirm">Turn off</string>
+    <!-- Negative button for a dialog warning message about revoking notification listener access. [CHAR_LIMIT=30] -->
+    <string name="notification_listener_revoke_warning_cancel">Cancel</string>
+    <!-- Title for screen controlling which apps have access to send premium sms messages. [CHAR_LIMIT=60] -->
+    <string name="premium_sms_access_title">Premium SMS access</string>
+    <!-- Message shown for option to enable Premium SMS for an app. [CHAR_LIMIT=NONE] -->
+    <string name="premium_sms_access_description">Premium SMS may cost you money and will add up to your carrier bills. If you enable permission for an app, you will be able to send premium SMS using that app.</string>
+    <!-- Title for managing apps which can query usage data. [CHAR_LIMIT=30] -->
+    <string name="usage_access_title">Usage access</string>
+    <!-- Description of the usage access permission. [CHAR_LIMIT=NONE] -->
+    <string name="usage_access_description">Usage access allows an app to track what other apps you\u2019re using and how often, as well as your carrier, language settings, and other details.</string>
+    <!-- Title for managing external storage directory access settings. [CHAR_LIMIT=30] -->
+    <string name="directory_access_title">Directory access</string>
+    <!-- String used to describe the name of a directory on a particular volume. Example: SD Card (Movies). [CHAR_LIMIT=50] -->
+    <string name="directory_on_volume"><xliff:g id="volume" example="SD Card">%1$s</xliff:g> (<xliff:g id="directory" example="Movies">%2$s</xliff:g>)</string>
+    <!-- Title for managing apps which can change Wi-Fi state. [CHAR_LIMIT=30] -->
+    <string name="wifi_control_title">Wi-Fi control</string>
+    <!-- Description of the change wifi state permission. [CHAR_LIMIT=NONE] -->
+    <string name="wifi_control_description">Wi-Fi control allows an app to turn Wi-Fi on or off, scan and connect to Wi-Fi networks, add or remove networks, or start a local-only hotspot.</string>
 
-    <!-- System --><skip />
+    <!-- Location --><skip/>
+    <!-- Main setting menu item to go into location settings [CHAR LIMIT=40] -->
+    <string name="location_settings_title">Location</string>
+    <!-- Title of Recent Location Requests setting [CHAR LIMIT=60] -->
+    <string name="location_settings_recent_requests_title">Recent Location Requests</string>
+    <!-- Message indicating that no apps have requested location recently [CHAR LIMIT=60] -->
+    <string name="location_settings_recent_requests_empty_message">No recent location requests</string>
+    <!-- Title of Location App-level Permissions setting [CHAR LIMIT=60] -->
+    <string name="location_settings_app_permissions_title">App-level permissions</string>
+    <!-- Title of Location Scanning setting [CHAR LIMIT=60] -->
+    <string name="location_settings_scanning_title">Scanning</string>
+    <!-- Title of Location Services category [CHAR LIMIT=60] -->
+    <string name="location_settings_services_title">Location Services</string>
+
+    <!-- Location Scanning --><skip/>
+    <!-- Title of Wi-Fi scanning preference [CHAR LIMIT=60] -->
+    <string name="location_settings_wifi_scanning_title">Wi\u2011Fi scanning</string>
+    <!-- Summary of Wi-Fi scanning preference [CHAR LIMIT=240] -->
+    <string name="location_settings_wifi_scanning_summary">Allow apps and services to scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. This can be used, for example, to improve location-based features and services.</string>
+    <!-- Title of Bluetooth scanning preference [CHAT LIMIT=60]-->
+    <string name="location_settings_bluetooth_scanning_title">Bluetooth scanning</string>
+    <!-- Summary of Bluetooth scanning preference [CHAR LIMIT=240] -->
+    <string name="location_settings_bluetooth_scanning_summary">Allow apps and services to scan for nearby devices at any time, even when Bluetooth is off. This can be used, for example, to improve location-based features and services.</string>
+
+    <!-- System --><skip/>
     <!-- Main setting menu item to go into system settings -->
     <string name="system_setting_title">System</string>
-    <!-- About phone screen, list item title.  Takes the user to the screen for seeing and installing system updates. [CHAR LIMIT=40] -->
-    <string name="system_update_settings_list_item_title">System updates</string>
-    <!-- About phone screen, list item summary.  Takes the user to the screen for seeing and installing system updates. [CHAR LIMIT=40] -->
-    <string name="system_update_settings_list_item_summary">""</string>
+    <!-- System updates title.  Takes the user to the screen for seeing and installing system updates. [CHAR LIMIT=40] -->
+    <string name="system_update_settings_title">System updates</string>
+
+    <!-- Settings About screen, status item value if the actual value is not available. [CHAR LIMIT=NONE]-->
+    <string name="status_unavailable">Unavailable</string>
 
     <!-- About phone screen, status item label  [CHAR LIMIT=40] -->
     <string name="firmware_version">Android version</string>
@@ -242,6 +565,10 @@
     <string name="kernel_version">Kernel version</string>
     <!-- About phone screen,  setting option name  [CHAR LIMIT=40] -->
     <string name="build_number">Build number</string>
+    <!-- About phone screen,  setting option name  [CHAR LIMIT=40] -->
+    <string name="bluetooth_mac_address">Bluetooth address</string>
+    <!-- About phone screen,  setting option name  [CHAR LIMIT=40] -->
+    <string name="about_wifi_mac_address">Wi-Fi MAC Address</string>
 
     <!-- About phone screen, show when a value of some status item is unavailable. -->
     <string name="device_info_not_available">Not available</string>
@@ -254,11 +581,11 @@
     <!-- About phone screen, summary of the item to go into the phone status screen -->
     <string name="device_status_summary" product="default">Phone number, signal, etc.</string>
 
-    <!-- About --> <skip />
+    <!-- About --><skip/>
     <!-- Main settings screen, setting title for the user to go into the About phone screen -->
     <string name="about_settings">About</string>
     <!-- Summary of device info page [CHAR LIMIT=NONE] -->
-    <string name="about_summary">Android <xliff:g id="version" example="6.0">%1$s</xliff:g></string>
+    <string name="about_summary">Android <xliff:g example="6.0" id="version">%1$s</xliff:g></string>
     <!-- Main settings screen, setting summary for the user to go into the About phone screen-->
     <string name="about_settings_summary">View legal info, status, software version</string>
     <!-- About phone settings screen, setting option name to go to dialog that shows legal info -->
@@ -271,24 +598,27 @@
     <string name="regulatory_labels">Regulatory labels</string>
     <!-- About phone settings screen, setting option name to show the safety and regulatory manual [CHAR LIMIT=40] -->
     <string name="safety_and_regulatory_info">Safety &amp; regulatory manual</string>
-    <!-- Note: this may be replaced by a more-specific title of the activity that will get launched --> <skip />
+    <!-- Note: this may be replaced by a more-specific title of the activity that will get launched --><skip/>
     <!-- About phone settings screen, setting option name to see copyright-related info -->
     <string name="copyright_title">Copyright</string>
-    <!-- Note: this may be replaced by a more-specific title of the activity that will get launched --> <skip />
+    <!-- Note: this may be replaced by a more-specific title of the activity that will get launched --><skip/>
     <!-- About phone settings screen, setting option name to see licensing info -->
     <string name="license_title">License</string>
-    <!-- Note: this may be replaced by a more-specific title of the activity that will get launched --> <skip />
+    <!-- Note: this may be replaced by a more-specific title of the activity that will get launched --><skip/>
     <!-- About phone settings screen, setting option name to see terms and conditions -->
     <string name="terms_title">Terms and conditions</string>
-    <!-- Note: this may be replaced by a more-specific title of the activity that will get launched --> <skip />
+    <!-- Note: this may be replaced by a more-specific title of the activity that will get launched --><skip/>
     <!-- About phone settings screen, setting option name to see licensing info for WebView component. [CHAR LIMIT=35] -->
-    <string name="webview_license_title">System WebView License</string>
+    <string name="webview_license_title">System WebView licenses</string>
     <!-- About phone settings screen, setting option name to see wallpapers attributions -->
     <string name="wallpaper_attributions">Wallpapers</string>
     <!-- About phone settings screen, setting option name to see wallpapers attributions values -->
     <string name="wallpaper_attributions_values">Satellite imagery providers:\n©2014 CNES / Astrium, DigitalGlobe, Bluesky</string>
 
-    <!-- Title for actual Settings license activity. --> <skip />
+    <!-- Text to display in regulatory info screen (from device overlay). [CHAR LIMIT=NONE] -->
+    <string name="regulatory_info_text"></string>
+
+    <!-- Title for actual Settings license activity. --><skip/>
     <!-- About phone settings, Legal information setting option name and title of dialog box holding license info -->
     <string name="settings_license_activity_title">Third-party licenses</string>
     <!-- About phone settings screen, Open source license dialog message when licenses cannot be loaded -->
@@ -296,23 +626,101 @@
     <!-- About phone settings screen, Open source license dialog title until license is fully loaded -->
     <string name="settings_license_activity_loading">Loading\u2026</string>
 
+    <!-- Developer Settings -->
+    <!-- Device Info screen. Countdown for user taps to enable development settings. [CHAR LIMIT=NONE] -->
+    <plurals name="show_dev_countdown">
+        <item quantity="one">You are now <xliff:g id="step_count">%1$d</xliff:g> step away from being a developer.</item>
+        <item quantity="other">You are now <xliff:g id="step_count">%1$d</xliff:g> steps away from being a developer.</item>
+    </plurals>
+    <!-- Device Info screen. Confirmation that developer settings are enabled. [CHAR LIMIT=NONE] -->
+    <string name="show_dev_on">You are now a developer!</string>
+    <!-- Device Info screen. Okay we get it, stop pressing, you already have it on. [CHAR LIMIT=NONE] -->
+    <string name="show_dev_already">No need, you are already a developer.</string>
+    <!-- Title of Developer options preference screen. [CHAR LIMIT=30] -->
+    <string name="developer_options_settings">Developer options</string>
 
-    <!-- Date and time settings --><skip />
+    <!-- Reset options --><skip/>
+    <!-- Title for a screen containing all device reset options. [CHAR LIMIT=50] -->
+    <string name="reset_options_title">Reset options</string>
+    <!-- Summary text for a screen containing all device reset options. [CHAR LIMIT=NONE] -->
+    <string name="reset_options_summary">Network, apps, or device reset</string>
+
+    <!-- Reset Network --><skip/>
+    <!-- Button title to reset Wi-Fi settings, mobile data settings, and Bluetooth settings. [CHAR LIMIT=40] -->
+    <string name="reset_network_title">Reset network</string>
+    <!-- Message on screen after user selects reset network settings. [CHAR LIMIT=NONE] -->
+    <string name="reset_network_desc">This will reset all network settings, including:</string>
+    <!-- List item for Wi-Fi on screen after user selects reset network settings. [CHAR LIMIT=NONE] -->
+    <string name="reset_network_item_wifi"><li>Wi\u2011Fi</li></string>
+    <!-- List item for Mobile data on screen after user selects reset network settings. [CHAR LIMIT=NONE] -->
+    <string name="reset_network_item_mobile"><li>Mobile data</li></string>
+    <!-- List item for Bluetooth on screen after user selects reset network settings. [CHAR LIMIT=NONE] -->
+    <string name="reset_network_item_bluetooth"><li>Bluetooth</li></string>
+    <!-- Title for option to reset all eSIMs for the vehicle. [CHAR LIMIT=50] -->
+    <string name="reset_esim_title">Erase all vehicle eSIMs</string>
+    <!-- Description text for option to reset all eSIMs for the vehicle. [CHAR LIMIT=NONE] -->
+    <string name="reset_esim_desc">This will not cancel your service plan.</string>
+    <!-- Title for error dialog when esim reset fails. [CHAR LIMIT=NONE] -->
+    <string name="reset_esim_error_title">Can\u2019t reset eSIMs</string>
+    <!-- Fallback representation of a network subscription that does not supply a display name, number, or carrier name. -->
+    <string name="reset_network_fallback_subscription_name" translatable="false">MCC:%s MNC:%s Slot:%s Id:%s</string>
+    <!-- Title of dialog to select network to reset. [CHAR LIMIT=30] -->
+    <string name="reset_network_select">Select network</string>
+    <!-- Button on screen after user selects reset network settings. [CHAR LIMIT=30] -->
+    <string name="reset_network_button_text">Reset settings</string>
+    <!-- Reset settings confirmation screen title. [CHAR LIMIT=30] -->
+    <string name="reset_network_confirm_title">Reset?</string>
+    <!-- Message on screen after user selects Reset settings button. [CHAR LIMIT=NONE] -->
+    <string name="reset_network_confirm_desc">Reset all network settings? You can\u2019t undo this action!</string>
+    <!-- Button on screen after user selects Reset settings button. [CHAR LIMIT=30] -->
+    <string name="reset_network_confirm_button_text">Reset settings</string>
+    <!-- Reset settings complete toast text. [CHAR LIMIT=75] -->
+    <string name="reset_network_complete_toast">Network settings have been reset</string>
+
+    <!-- Reset apps --><skip/>
+    <!-- Button title to reset all of user's app preferences. [CHAR LIMIT=40] -->
+    <string name="reset_app_pref_title">Reset app preferences</string>
+    <!-- Message on screen after user selects reset app preferences. [CHAR LIMIT=NONE] -->
+    <string name="reset_app_pref_desc">This will reset all preferences for:\n\n<li>Disabled apps</li>\n<li>Disabled app notifications</li>\n<li>Default applications for actions</li>\n<li>Background data restrictions for apps</li>\n<li>Any permission restrictions</li>\n\nYou will not lose any app data.
+    </string>
+    <!-- Button on screen after user selects reset app preferences. [CHAR LIMIT=30] -->
+    <string name="reset_app_pref_button_text">Reset apps</string>
+    <!-- Reset app preferences complete toast text. [CHAR LIMIT=75] -->
+    <string name="reset_app_pref_complete_toast">App preferences have been reset</string>
+
+    <!-- Master Clear --><skip/>
+    <!-- Button title to factory data reset the entire device. The "(factory reset)" part is optional for translation. [CHAR LIMIT=40]-->
+    <string name="master_clear_title">Erase all data (factory reset)</string>
+    <!-- Message on screen after user selects factory reset. [CHAR LIMIT=NONE] -->
+    <string name="master_clear_desc">This will erase all data from your vehicle\u2019s head unit, including:\n\n<li>Your Google account</li>\n<li>System and app data and settings</li>\n<li>Downloaded apps</li></string>
+    <!-- The list of current accounts follows this text. [CHAR LIMIT=NONE] -->
+    <string name="master_clear_accounts">You are currently signed into the following accounts:</string>
+    <!-- Message on screen if other users are present on the device after user selects factory reset. [CHAR LIMIT=NONE] -->
+    <string name="master_clear_other_users_present">There are other users present on this vehicle.</string>
+    <!-- Button on screen after user selects factory reset. [CHAR LIMIT=30] -->
+    <string name="master_clear_button_text">Reset vehicle</string>
+    <!-- Master clear confirmation screen title. [CHAR LIMIT=30] -->
+    <string name="master_clear_confirm_title">Reset?</string>
+    <!-- Message on screen after user selects Reset device button. [CHAR LIMIT=NONE] -->
+    <string name="master_clear_confirm_desc">Erase all your personal information and downloaded apps?  You can\u2019t undo this action!</string>
+    <string name="master_clear_confirm_button_text">Erase everything</string>
+    <!-- Master clear progress screen title. [CHAR LIMIT=30] -->
+    <string name="master_clear_progress_title" >Erasing</string>
+    <!-- Master clear progress screen text. [CHAR LIMIT=75] -->
+    <string name="master_clear_progress_text">Please wait...</string>
+
+    <!-- Date and time settings --><skip/>
     <!-- Main Settings screen setting option name to go into the date and time settings-->
     <string name="date_and_time_settings_title">Date &amp; time</string>
-    <!-- Title for Date & Time settings screen in SetupWizard [CHAR LIMIT=40] -->
-    <string name="date_and_time_settings_title_setup_wizard">Set date and time</string>
     <!-- Main Settings screen setting option summary text for the item to go into the date and time settings. -->
     <string name="date_and_time_settings_summary">Set date, time, time zone, &amp; formats</string>
     <!-- Date & time setting screen setting check box title if the date and time should be determined automatically [CHAR LIMIT=30] -->
     <string name="date_time_auto">Automatic date &amp; time</string>
-    <!-- Date & time setting screen setting option summary text when Automatic check box is selected
-        (that is, when date and time should be determined automatically) [CHAR LIMIT=100] -->
+    <!-- Date & time setting screen setting option summary text when Automatic check box is selected (that is, when date and time should be determined automatically) [CHAR LIMIT=100] -->
     <string name="date_time_auto_summary">Use network-provided time</string>
     <!-- Date & time setting screen setting check box title if the time zone should be determined automatically [CHAR LIMIT=30] -->
     <string name="zone_auto">Automatic time zone</string>
-    <!-- Date & time setting screen setting option summary text when Automatic time zone check box is selected (that is, when date and time should be determined automatically)
-        [CHAR LIMIT=100]  -->
+    <!-- Date & time setting screen setting option summary text when Automatic time zone check box is selected (that is, when date and time should be determined automatically) [CHAR LIMIT=100]  -->
     <string name="zone_auto_summary">Use network-provided time zone</string>
     <!-- Date & time setting screen setting check box title [CHAR LIMIT=30] -->
     <string name="date_time_24hour_title">24\u2011hour format</string>
@@ -334,27 +742,96 @@
     <string name="zone_list_menu_sort_alphabetically">Sort alphabetically</string>
     <!-- Menu item on Select time zone screen -->
     <string name="zone_list_menu_sort_by_timezone">Sort by time zone</string>
-    <!-- Title string shown above DatePicker, letting a user select system date
-         [CHAR LIMIT=20] -->
+    <!-- Title string shown above DatePicker, letting a user select system date [CHAR LIMIT=20] -->
     <string name="date_picker_title">Date</string>
-    <!-- Title string shown above TimePicker, letting a user select system time
-         [CHAR LIMIT=20] -->
+    <!-- Title string shown above TimePicker, letting a user select system time [CHAR LIMIT=20] -->
     <string name="time_picker_title">Time</string>
 
-    <!-- User settings add user menu [CHAR LIMIT=35] -->
-    <string name="user_add_user_menu">Add user</string>
-    <!-- Button title for adding an account [CHAR LIMIT=35] -->
-    <string name="user_add_account_menu">Add account</string>
-    <!-- Spoken content description for delete icon beside a user [CHAR LIMIT=none] -->
-    <string name="user_delete_user_description">Delete user</string>
-    <!-- User details new user name [CHAR LIMIT=30] -->
-    <string name="user_new_user_name">New user</string>
-    <!-- Title for Guest user [CHAR LIMIT=35] -->
-    <string name="user_guest">Guest</string>
+    <!-- Admin user management --><skip/>
     <!-- Title for Admin user [CHAR LIMIT=35] -->
     <string name="user_admin">Admin</string>
-    <!-- User summary to indicate Admin user is currently logged in[CHAR LIMIT=35] -->
+    <!-- User summary to be displayed next to the currently logged-in user if they are also an admin. [CHAR LIMIT=35] -->
     <string name="signed_in_admin_user">Signed in as admin</string>
+    <!-- Title for the dialog that grants the user admin permissions. [CHAR LIMIT=50]-->
+    <string name="grant_admin_permissions_title">All admin permissions</string>
+    <!-- Button text for granting admin permissions to a user [CHAR LIMIT=35]-->
+    <string name="grant_admin_permissions_button_text">Make Admin</string>
+    <!-- Message to inform the user of what will happen when another user is upgraded to Admin status. [CHAR LIMIT=NONE]-->
+    <string name="grant_admin_permissions_message">The user will be able to delete users, including other Admins, and factory reset the system.</string>
+    <!-- Message to inform the user that granting admin permissions to another user is not reversible. [CHAR LIMIT=100]-->
+    <string name="action_not_reversible_message">This action is not reversible.</string>
+    <!-- Button text to confirm granting admin permissions to another user. [CHAR LIMIT=40]-->
+    <string name="confirm_grant_admin">Yes, make admin</string>
+
+    <!-- User Restrictions --><skip/>
+    <!-- Creating Users --><skip/>
+    <!-- Title for toggle that controls whether a user is able to create other users [CHAR LIMIT=35] -->
+    <string name="create_user_permission_title">Create new users</string>
+
+    <!-- Outgoing Calls --><skip/>
+    <!-- Title for toggle that controls whether a user is able to make outgoing calls [CHAR LIMIT=35] -->
+    <string name="outgoing_calls_permission_title">Make phone calls</string>
+
+    <!-- SMS Messaging --><skip/>
+    <!-- Title for toggle that controls whether a user is able to send and receive sms messages using the car's mobile data [CHAR LIMIT=55] -->
+    <string name="sms_messaging_permission_title">Messaging via car\u0027s mobile data</string>
+
+    <!-- Apps --><skip/>
+    <!-- Title for toggle that controls whether a user is able to install new apps [CHAR LIMIT=35] -->
+    <string name="install_apps_permission_title">Install new apps</string>
+    <!-- Title for toggle that controls whether a user is able to uninstall apps [CHAR LIMIT=35] -->
+    <string name="uninstall_apps_permission_title">Uninstall apps</string>
+
+    <!-- Adding new users --><skip/>
+    <!-- User settings add user menu [CHAR LIMIT=35] -->
+    <string name="user_add_user_menu">Add user</string>
+    <!-- User details new user name [CHAR LIMIT=30] -->
+    <string name="user_new_user_name">New user</string>
+    <!-- Title of the alert dialog to ask user to confirm creation of new user. [CHAR LIMIT=30] -->
+    <string name="user_add_user_title">"Add new user?"</string>
+    <!-- Message to inform user that creation of new user requires that user to set up their space. [CHAR LIMIT=100] -->
+    <string name="user_add_user_message_setup">When you add a new user, that person needs to set up their space.</string>
+    <!-- Message to inform user that the newly created user will have permissions to update apps for all other users. [CHAR LIMIT=100] -->
+    <string name="user_add_user_message_update">Any user can update apps for all other users.</string>
+    <!-- Title for the dialog that lets users know that the maximum allowed number of users on the device has been reached. [CHAR LIMIT=35]-->
+    <string name="user_limit_reached_title">User limit reached</string>
+    <!-- Message that tells people what's the maximum number of uses allowed on the device. [CHAR_LIMIT=NONE]-->
+    <plurals name="user_limit_reached_message">
+        <item quantity="one">Only one user can be created.</item>
+        <item quantity="other">You can create up to <xliff:g example="3" id="count">%d</xliff:g> users.</item>
+    </plurals>
+    <!-- Title of the alert dialog to notify that the creation of new user has failed. [CHAR LIMIT=40] -->
+    <string name="add_user_error_title">Failed to create a new user</string>
+
+    <!-- Deleting users --><skip/>
+    <!-- Title of the alert dialog to ask user to confirm user deletion [CHAR LIMIT=40] -->
+    <string name="delete_user_dialog_title">Delete this user?</string>
+    <!-- Message to inform user that all of user's data will be deleted if confirmed [CHAR LIMIT=NONE] -->
+    <string name="delete_user_dialog_message">"All apps and data will be deleted."</string>
+    <!-- Title of the alert dialog to notify that user deletion has failed. [CHAR LIMIT=40] -->
+    <string name="delete_user_error_title">Failed to delete user.</string>
+    <!-- Button label to dismiss user deletion error [CHAR LIMIT=25] -->
+    <string name="delete_user_error_dismiss">Dismiss</string>
+    <!-- Button label to retry user deletion [CHAR LIMIT=25] -->
+    <string name="delete_user_error_retry">Retry</string>
+    <!-- Title of the alert dialog to ask user to confirm deletion of the last user on the device. [CHAR LIMIT=40] -->
+    <string name="delete_last_user_dialog_title">Delete last user?</string>
+    <!-- Message to inform user that deletion of the last user will prompt the creation of a new admin user. [CHAR LIMIT=NONE] -->
+    <string name="delete_last_user_admin_created_message">"After deleting the only remaining user for this car, a new admin user will be created."</string>
+    <!-- Message to inform user that deletion of the last user will require new system setup. [CHAR LIMIT=NONE] -->
+    <string name="delete_last_user_system_setup_required_message">"All data, settings, and apps associated with this user will be deleted. You'll need to set up the system again."</string>
+    <!-- Title of the alert dialog to notify that new admin should be chosen. [CHAR LIMIT=40] -->
+    <string name="choose_new_admin_title">Choose new admin</string>
+    <!-- Message to notify that new admin must be chosen in order to delete the current one. [CHAR LIMIT=150] -->
+    <string name="choose_new_admin_message">You need at least one admin. To delete this one, first choose a replacement.</string>
+    <!-- Label for choosing new admin. [CHAR LIMIT=30] -->
+    <string name="choose_new_admin_label">Choose admin</string>
+
+    <!-- User info --><skip/>
+    <!-- Title for Guest user [CHAR LIMIT=35] -->
+    <string name="user_guest">Guest</string>
+    <!-- Title for button for starting a new guest session [CHAR LIMIT=35] -->
+    <string name="start_guest_session">Guest</string>
     <!-- Description for icon to switch user [CHAR LIMIT=35] -->
     <string name="user_switch">Switch</string>
     <!-- Description for user name that is currently in use -->
@@ -365,17 +842,71 @@
     <string name="user_summary_not_set_up">Not set up</string>
     <!-- Title for edit user name page [CHAR LIMIT=30] -->
     <string name="edit_user_name_title">Edit user name</string>
-
     <!-- User settings header for list of users on the system. [CHAR LIMIT=35] -->
     <string name="users_list_title">Users</string>
+    <!-- Title for the user details page that shows the permissions granted to the user. The parameter is the name of the user. [CHAR LIMIT=40] -->
+    <string name="user_details_admin_title">Permissions granted to %1$s</string>
+
+    <!-- Storage management --><skip/>
+    <!-- Title for settings that lead into information about device storage [CHAR LIMIT=35] -->
+    <string name="storage_settings_title">Storage</string>
+    <!-- Preference label for the Music & Audio storage section. [CHAR LIMIT=50] -->
+    <string name="storage_music_audio">Music &amp; audio</string>
+    <!-- Preference label for the Other apps storage section. [CHAR LIMIT=50] -->
+    <string name="storage_other_apps">Other apps</string>
+    <!-- Preference label for the Files storage section. [CHAR LIMIT=50] -->
+    <string name="storage_files">Files</string>
+    <!-- Preference label for the System storage section. [CHAR LIMIT=50] -->
+    <string name="storage_system">System</string>
+    <!-- Body of dialog informing user about the storage used by the Android System [CHAR LIMIT=NONE]-->
+    <string name="storage_detail_dialog_system">System includes files used to run Android version <xliff:g id="version" example="8.0">%s</xliff:g></string>
+    <!-- Car storage settings summary. Displayed when the total memory usage is being calculated. Will be replaced with a number like "12.3 GB" when finished calculating. [CHAR LIMIT=30] -->
+    <!-- Title for preference on storage usage page that opens audio file browsing. [CHAR LIMIT=50] -->
+    <string name="storage_audio_files_title">Audio files</string>
+   <!-- Car storage settings summary. Displayed when the total memory usage is being calculated. Will be replaced with a number like "12.3 GB" when finished calculating. [CHAR LIMIT=30] -->
+    <string name="memory_calculating_size">Calculating\u2026</string>
+    <!-- Individual application info screen, label under Storage heading. The amount of space taken up by the application itself (for example, the java compiled files) [CHAR LIMIT=35] -->
+    <string name="storage_application_size_label">App size</string>
+    <!-- Individual application info screen, label under Storage heading.  The amount of space taken up by the app's data (for example, downloaded emails) [CHAR LIMIT=35] -->
+    <string name="storage_data_size_label">User data</string>
+    <!-- Label that appears next to the cache size [CHAR LIMIT=35] -->
+    <string name="storage_cache_size_label">Cache</string>
+    <!-- Individual application info screen,label under Storage heading.  The total storage space taken up by this app. [CHAR LIMIT=35] -->
+    <string name="storage_total_size_label">Total</string>
+    <!-- Individual application info screen, button label under Storage heading. Button to clear all data associated with tis app (for example, remove all cached emails for an Email app) [CHAR LIMIT=35] -->
+    <string name="storage_clear_user_data_text">Clear storage</string>
+    <!-- Individual application info screen, button label under Storage heading. Text label for button [CHAR LIMIT=35] -->
+    <string name="storage_clear_cache_btn_text">Clear cache</string>
+    <!-- Individual application screen, confirmation dialog title. Displays when user selects to "Clear data". [CHAR LIMIT=45] -->
+    <string name="storage_clear_data_dlg_title">Delete app data?</string>
+    <!-- Individual application screen, confirmation dialog message. Displays when user selects to "Clear data". It warns the user of the consequences of clearing the data for an app. [CHAR LIMIT=200] -->
+    <string name="storage_clear_data_dlg_text">All this app\u2019s data will be deleted permanently. This includes all files, settings, accounts, databases, etc.</string>
+    <!-- Text for dialog if clear data fails. [CHAR LIMIT=55] -->
+    <string name="storage_clear_failed_dlg_text">Couldn\u2019t clear storage for app.</string>
+
+    <!-- Account management --><skip/>
     <!-- Title for settings that lead into information about User's accounts [CHAR LIMIT=35] -->
     <string name="accounts_settings_title">Accounts</string>
-    <!-- Title for detailed user information page [CHAR LIMIT=25] -->
-    <string name="user_details_title">User</string>
+    <!-- Button title for adding an account [CHAR LIMIT=35] -->
+    <string name="user_add_account_menu">Add account</string>
     <!-- Text to display in Accounts settings if the user has not added any accounts yet [CHAR LIMIT=40] -->
     <string name="no_accounts_added">No accounts added</string>
     <!-- Account settings header for list of added accounts [CHAR LIMIT=40] -->
-    <string name="account_list_title">Accounts for <xliff:g id="current_user_name">%1$s</xliff:g></string>
+    <string name="account_list_title">Accounts for
+        <xliff:g id="current_user_name">%1$s</xliff:g>
+    </string>
+    <!-- Switch label to enable auto sync account [CHAR LIMIT=60] -->
+    <string name="account_auto_sync_title">Automatically sync data</string>
+    <!-- Switch summary to enable auto sync account [CHAR LIMIT=60] -->
+    <string name="account_auto_sync_summary">Let apps refresh data automatically</string>
+    <!--  Title of dialog shown when user enables global auto sync [CHAR LIMIT=50] -->
+    <string name="data_usage_auto_sync_on_dialog_title">Turn auto-sync data on?</string>
+    <!--  Body of dialog shown when user enables global auto sync [CHAR LIMIT=NONE] -->
+    <string name="data_usage_auto_sync_on_dialog">Any changes you make to your accounts on the web will be automatically copied to your device.\n\nSome accounts may also automatically copy any changes you make on the phone to the web. A Google Account works this way.</string>
+    <!--  Title of dialog shown when user disables global auto sync [CHAR LIMIT=50] -->
+    <string name="data_usage_auto_sync_off_dialog_title">Turn auto-sync data off?</string>
+    <!--  Body of dialog shown when user disables global auto sync [CHAR LIMIT=NONE] -->
+    <string name="data_usage_auto_sync_off_dialog">This will conserve data, but you\u2019ll need to sync each account manually to collect recent information. And you won\u2019t receive notifications when updates occur.</string>
     <!-- Account details in Settings screen title [CHAR LIMIT=25] -->
     <string name="account_details_title">Account info</string>
     <!-- Account details in Settings screen title [CHAR LIMIT=25] -->
@@ -390,28 +921,34 @@
     <string name="really_remove_account_title">Remove account?</string>
     <!-- Remove account message in dialog [CHAR LIMIT=NONE] -->
     <string name="really_remove_account_message">Removing this account will delete all of its messages, contacts, and other data from the device!</string>
-    <!-- This is shown if the authenticator for a given account fails to remove it. [CHAR LIMIT=NONE] -->
-    <string name="remove_account_failed">This change isn\'t allowed by your admin</string>
-    <!-- Title of the alert dialog to ask user to confirm user deletion [CHAR LIMIT=25] -->
-    <string name="really_remove_user_title">Remove this user?</string>
-    <!-- Message to inform user that all of user's data will be deleted if confirmed [CHAR LIMIT=NONE] -->
-    <string name="really_remove_user_message">"All apps and data will be deleted."</string>
-    <!-- Title of the alert dialog to ask user to confirm user deletion [CHAR LIMIT=40] -->
-    <string name="remove_user_error_title">Failed to remove user.</string>
-    <!-- Message to ask to retry user deletion [CHAR LIMIT=30] -->
-    <string name="remove_user_error_message">Try again?</string>
-    <!-- Button label to dismiss user deletion error [CHAR LIMIT=25] -->
-    <string name="remove_user_error_dismiss">Dismiss</string>
-    <!-- Button label to retry user deletion [CHAR LIMIT=25] -->
-    <string name="remove_user_error_retry">Retry</string>
-    <!-- Title of the alert dialog to ask user to confirm creation of new user. [CHAR LIMIT=30] -->
-    <string name="user_add_user_title">"Add new user?"</string>
-    <!-- Message to inform user that creation of new user requires that user to set up their space. [CHAR LIMIT=100] -->
-    <string name="user_add_user_message_setup">When you add a new user, that person needs to set up their space.</string>
-    <!-- Message to inform user that the newly created user will have permissions to update apps for all other users. [CHAR LIMIT=100] -->
-    <string name="user_add_user_message_update">Any user can update apps for all other users.</string>
+    <!-- Title of the alert dialog to notify that account deletion has failed. [CHAR LIMIT=40] -->
+    <string name="remove_account_error_title">Failed to remove account.</string>
+    <!-- Preference label to sync account [CHAR LIMIT=60] -->
+    <string name="account_sync_title">Account sync</string>
+    <!-- Preference summary for account sync when syncing is on for some items [CHAR LIMIT=NONE] -->
+    <string name="account_sync_summary_some_on">Sync on for <xliff:g name="count" example="5">%1$d</xliff:g> of <xliff:g name="total" example="8">%2$d</xliff:g> items</string>
+    <!-- Preference summary for account sync when syncing is on for everything [CHAR LIMIT=NONE] -->
+    <string name="account_sync_summary_all_on">Sync on for all items</string>
+    <!-- Preference summary for account sync when syncing is off for everything [CHAR LIMIT=NONE] -->
+    <string name="account_sync_summary_all_off">Sync off for all items</string>
+    <!-- Sync status shown when sync is disabled [CHAR LIMIT=25] -->
+    <string name="sync_disabled">Sync is OFF</string>
+    <!-- Sync status shown when last sync resulted in an error [CHAR LIMIT=25] -->
+    <string name="sync_error">Sync error</string>
+    <!-- Last synced date/time for accounts that synced [CHAR LIMIT=none] -->
+    <string name="last_synced">Last synced <xliff:g id="last_sync_time" example="12/05/18, 12:30 PM">%1$s</xliff:g></string>
+    <!-- Sync status shown when sync is in progress [CHAR LIMIT=30] -->
+    <string name="sync_in_progress">Syncing now\u2026</string>
+    <!-- Data synchronization settings screen, summary of a sync provider (for example, Gmail) when autosync is off and the user wants to do a one-time sync. The last synced time will be shown on a new line. [CHAR LIMIT=none] -->
+    <string name="sync_one_time_sync">Tap to sync now<xliff:g id="last_sync_time" example="\nLast synced 12/05/18, 12:30 PM">\n%1$s</xliff:g></string>
+    <!-- Data synchronization settings screen, text for button that allows the user to immediately start sync [CHAR LIMIT=30]-->
+    <string name="sync_button_sync_now">Sync now</string>
+    <!-- Data synchronization settings screen, text for button that allows the user to cancel sync [CHAR LIMIT=30]-->
+    <string name="sync_button_sync_cancel">Cancel sync</string>
+    <!-- Message when sync is currently failing [CHAR LIMIT=100] -->
+    <string name="sync_is_failing">Sync is currently experiencing problems. It will be back shortly.</string>
 
-    <!-- security lock -->
+    <!-- security lock --><skip/>
     <!-- Title for security settings [CHAR LIMIT=20] -->
     <string name="security_settings_title">Security</string>
     <!-- Subtitle for security settings [CHAR LIMIT=20] -->
@@ -426,8 +963,8 @@
     <string name="security_lock_password">Password</string>
     <!--  Title for security picker to choose the unlock method: None/Pattern/PIN/Password [CHAR LIMIT=30] -->
     <string name="lock_settings_picker_title">Choose a lock type</string>
-    <!--  Button caption to open a dialog for user to choose an screen lock method: Pattern/PIN/Password [CHAR LIMIT=40] -->
-    <string name="screen_lock_options">Screen lock options</string>
+    <!--  Button caption to open a dialog for user to choose an screen lock method: Pattern/PIN/Password [CHAR LIMIT=20] -->
+    <string name="screen_lock_options">Lock options</string>
     <!--  Title for screen asking user to enter their existing lock pattern [CHAR LIMIT=30] -->
     <string name="lock_settings_enter_pattern">Enter your pattern</string>
     <!-- Security & location settings screen, change unlock pattern screen button, on bottom of screen.  After they draw a pattern and release their finger, we display the pattern so they remember.  When they are ready to draw it once again to confirm it, they press this button. -->
@@ -447,7 +984,7 @@
     <!-- Title string shown in choose lock pattern [CHAR LIMIT=40] -->
     <string name="lockscreen_choose_your_pattern">Choose your pattern</string>
     <!-- Title string shown in choose lock password [CHAR LIMIT=40] -->
-    <string name="lockscreen_choose_your_password">Choose your passowrd</string>
+    <string name="lockscreen_choose_your_password">Choose your password</string>
     <!-- Subtitle indicating the current screen lock type (none/PIN/pattern/password) [CHAR LIMIT=40] -->
     <string name="current_screen_lock">Current screen lock</string>
     <!-- Message on first screen of choose pattern flow [CHAR LIMIT=40] -->
@@ -516,16 +1053,14 @@
     <!-- Hints for Choose Lock Password Screen [CHAR_LIMIT=73] -->
     <string name="choose_lock_password_hints">Must be at least 4 characters</string>
     <!-- Hint shown in dialog screen when password is too short [CHAR LIMIT=40] -->
-    <string name="lockpassword_password_too_short">Must be at least <xliff:g id="count" example="3">%d</xliff:g> characters</string>
+    <string name="lockpassword_password_too_short">Must be at least <xliff:g example="3" id="count">%d</xliff:g> characters</string>
     <!-- Hint shown in dialog screen when PIN is too short [CHAR LIMIT=44] -->
-    <string name="lockpassword_pin_too_short">PIN must be at least <xliff:g id="count" example="3">%d</xliff:g> digits</string>
+    <string name="lockpassword_pin_too_short">PIN must be at least <xliff:g example="3" id="count">%d</xliff:g> digits</string>
 
     <!-- Error shown in popup when password is too long [CHAR LIMIT=42] -->
-    <string name="lockpassword_password_too_long">Must be fewer than
-        <xliff:g id="number" example="17">%d</xliff:g> characters</string>
+    <string name="lockpassword_password_too_long">Must be fewer than <xliff:g example="17" id="number">%d</xliff:g> characters</string>
     <!-- Error shown in popup when PIN is too long [CHAR LIMIT=42] -->
-    <string name="lockpassword_pin_too_long">Must be fewer than
-        <xliff:g id="number" example="17">%d</xliff:g> digits</string>
+    <string name="lockpassword_pin_too_long">Must be fewer than <xliff:g example="17" id="number">%d</xliff:g> digits</string>
 
     <!-- Error shown when in PIN mode and user enters a non-digit. Because this may be concatenated with other messages, please make sure there's a period at the end of the sentence. [CHAR LIMIT=40] -->
     <string name="lockpassword_pin_contains_non_digits">Must contain only digits 0-9.</string>
@@ -545,42 +1080,41 @@
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of letters [CHAR LIMIT=NONE] -->
     <plurals name="lockpassword_password_requires_letters">
         <item quantity="one">Must contain at least 1 letter</item>
-        <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> letters</item>
+        <item quantity="other">Must contain at least <xliff:g example="3" id="count">%d</xliff:g> letters</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of lowercase letters [CHAR LIMIT=NONE] -->
     <plurals name="lockpassword_password_requires_lowercase">
         <item quantity="one">Must contain at least 1 lowercase letter</item>
-        <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> lowercase letters</item>
+        <item quantity="other">Must contain at least <xliff:g example="3" id="count">%d</xliff:g> lowercase letters</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of uppercase letters [CHAR LIMIT=NONE] -->
     <plurals name="lockpassword_password_requires_uppercase">
         <item quantity="one">Must contain at least 1 uppercase letter</item>
-        <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> uppercase letters</item>
+        <item quantity="other">Must contain at least <xliff:g example="3" id="count">%d</xliff:g> uppercase letters</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of numerical digits [CHAR LIMIT=NONE] -->
     <plurals name="lockpassword_password_requires_numeric">
         <item quantity="one">Must contain at least 1 numerical digit</item>
-        <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> numerical digits</item>
+        <item quantity="other">Must contain at least <xliff:g example="3" id="count">%d</xliff:g> numerical digits</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of special symbols [CHAR LIMIT=NONE] -->
     <plurals name="lockpassword_password_requires_symbols">
         <item quantity="one">Must contain at least 1 special symbol</item>
-        <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> special symbols</item>
+        <item quantity="other">Must contain at least <xliff:g example="3" id="count">%d</xliff:g> special symbols</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of non-letter characters [CHAR LIMIT=NONE] -->
     <plurals name="lockpassword_password_requires_nonletter">
         <item quantity="one">Must contain at least 1 non-letter character</item>
-        <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> non-letter characters</item>
+        <item quantity="other">Must contain at least <xliff:g example="3" id="count">%d</xliff:g> non-letter characters</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password has been used recently. Please keep this string short! [CHAR LIMIT=NONE] -->
-    <string name="lockpassword_password_recently_used">Device admin doesn\'t allow using a recent
-        password</string>
+    <string name="lockpassword_password_recently_used">Device admin doesn\'t allow using a recent password</string>
 
     <!-- Message shown when error is encountered when saving password [CHAR LIMIT=50] -->
     <string name="error_saving_password">Error saving password</string>
@@ -594,15 +1128,27 @@
     <!-- Label for button in screen lock settings, allowing users to choose other types of screen locks. [CHAR LIMIT=40] -->
     <string name="setup_lock_settings_options_button_label">Screen lock options</string>
 
-    <!-- generic -->
+    <!-- Format of the message displayed at the bottom of the quick settings page, that shows build information in
+         user-debug and eng builds. -->
+    <string name="build_info_fmt"><xliff:g example="google/gcar_emu_x86/..." id="fingerprint">%1$s</xliff:g>\n<xliff:g example="Thu Aug 30" id="date">%2$s</xliff:g> : <xliff:g example="4" id="num_days">%3$s</xliff:g> days ago</string>
+
+    <!-- generic --><skip/>
     <!-- Button label for generic forget action [CHAR LIMIT=20] -->
     <string name="forget">Forget</string>
+    <!-- Button label for generic connect action [CHAR LIMIT=20] -->
+    <string name="connect">Connect</string>
+    <!-- Button label for generic disconnect action [CHAR LIMIT=20] -->
+    <string name="disconnect">Disconnect</string>
     <!-- Delete button text [CHAR LIMIT=20] -->
     <string name="delete_button">Delete</string>
     <!-- Remove button text [CHAR LIMIT=20] -->
     <string name="remove_button">Remove</string>
     <!-- Cancel button text [CHAR LIMIT=20] -->
     <string name="cancel">Cancel</string>
+    <!-- Generic dialog allow button. [CHAR_LIMIT=20] -->
+    <string name="allow">Allow</string>
+    <!-- Generic dialog deny button. [CHAR_LIMIT=20] -->
+    <string name="deny">Deny</string>
 
     <!-- Accessibility description for the backspace key in the PIN pad [CHAR LIMIT=NONE] -->
     <string name="backspace_key">Backspace key</string>
@@ -621,26 +1167,21 @@
     <string name="nine" translatable="false">9</string>
     <string name="zero" translatable="false">0</string>
 
-    <!-- Demo/Retail-related strings -->
+    <!-- Demo/Retail-related strings --><skip/>
     <!-- Text of button the user presses to exit Retail/Demo mode [CHAR LIMIT=25]-->
     <string name="exit_retail_button_text">Exit Demo</string>
-    <!-- Title of dialog asking the user if they really want to exit the demo, which requires a
-         factory reset [CHAR LIMIT=30]-->
+    <!-- Title of dialog asking the user if they really want to exit the demo, which requires a factory reset [CHAR LIMIT=30]-->
     <string name="exit_retail_mode_dialog_title">Exit demo mode</string>
-    <!-- Message in dialog asking the user if they really want to exit the demo, which requires a
-         factory reset [CHAR LIMIT=150]-->
+    <!-- Message in dialog asking the user if they really want to exit the demo, which requires a factory reset [CHAR LIMIT=150]-->
     <string name="exit_retail_mode_dialog_body">This will delete the demo account and factory data reset the system. All user data will be lost.</string>
     <!-- Text of confirmation button in dialog that appears to confirm that Demo Mode should be exited. [CHAR LIMIT=25]-->
     <string name="exit_retail_mode_dialog_confirmation_button_text">Exit Demo</string>
 
-    <!-- String values for settings suggestions. -->
-    <!-- Button that allows users to re-open the setup wizard (changed to all caps so case doesn't matter) [CHAR_LIMIT=30] -->
-    <string name="suggestion_primary_button">finish setup</string>
-    <!-- Button that dismisses the suggestion to finish setting up their device (changed to all caps so case doesn't matter) [CHAR_LIMIT=30] -->
-    <string name="suggestion_secondary_button">not now</string>
+    <!-- String values for settings suggestions. --><skip/>
+    <!-- Button that dismisses the suggestion to finish setting up their device [CHAR_LIMIT=30] -->
+    <string name="suggestion_dismiss_button">DISMISS</string>
 
-    <!-- Warn user that the action they are trying to perform is blocked while the car is in
-         motion [CHAR LIMIT=60] -->
+    <!-- Warn user that the action they are trying to perform is blocked while the car is in motion [CHAR LIMIT=60] -->
     <string name="restricted_while_driving">Feature not available while driving.</string>
     <!-- Warn user that adding a user is blocked while the car is in motion [CHAR LIMIT=60] -->
     <string name="add_user_restricted_while_driving">Can\'t add user while driving.</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fb9b96a..fa0195c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -1,90 +1,66 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!--
+    Copyright 2018 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
+    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
+         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.
+    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.
 -->
 
 <resources>
-    <style name="Theme.SettingsBase" parent="@android:style/Theme.Material.Settings" />
-
-    <attr name="WifiSignalColor" format="reference" />
-
-    <style name="CarSettingTheme" parent="Theme.Car.Light.NoActionBar">
-        <item name="wifi_signal">@drawable/wifi_signal</item>
-        <item name="WifiSignalColor">?android:attr/colorAccent</item>
-        <item name="android:windowBackground">@color/car_card</item>
-        <item name="android:windowAnimationStyle">@style/SettingAnimationStyle</item>
-        <item name="android:borderlessButtonStyle">@style/Widget.Car.Settings.ActionBar.Button.Borderless.Colored</item>
+    <style name="PreferenceButtonTextAppearance"
+           parent="@*android:style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored">
+        <item name="android:fadingEdgeLength">@dimen/button_fading_edge_length</item>
+        <item name="android:ellipsize">none</item>
+        <item name="android:requiresFadingEdge">horizontal</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textAllCaps">false</item>
     </style>
 
-    <style name="SettingAnimationStyle">
-        <item name="android:windowEnterAnimation">@anim/trans_fade_in</item>
-        <item name="android:windowExitAnimation">@anim/trans_fade_out</item>
-    </style>
-
-    <style name="ActionBarStyle.Car" parent="Widget.Car.Toolbar">
-        <item name="actionBarSize">@dimen/car_app_bar_height</item>
-    </style>
-
-    <style name="TrimmedHorizontalProgressBar" parent="android:Widget.Material.ProgressBar.Horizontal">
-        <item name="android:indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material_trimmed</item>
+    <style name="TrimmedHorizontalProgressBar"
+           parent="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal">
+        <item name="android:indeterminateDrawable">
+            @drawable/progress_indeterminate_horizontal_material_trimmed
+        </item>
         <item name="android:minHeight">3dp</item>
         <item name="android:maxHeight">3dp</item>
     </style>
 
-    <style name="ListIcon.ActionBar" parent="ListIcon">
-        <item name="android:layout_gravity">center</item>
-        <item name="android:src">@drawable/ic_arrow_back</item>
-    </style>
-
-    <style name="SettingsListHeader" parent="TextAppearance.Car.Body2">
-        <item name="android:textColor">@color/car_accent</item>
-    </style>
-
-    <style name="Widget.Car.Settings.ActionBar.Button.Borderless.Colored"
-           parent="Widget.Car.Button.Borderless.Colored">
-        <item name="android:minWidth">@dimen/car_button_min_width</item>
-        <item name="android:fontFamily">roboto-regular</item>
-    </style>
-
-    <style name="SetupWizardButton.Negative" parent="@style/SuwGlifButton.Secondary">
-        <!-- Negative margin to offset for padding of the button itself. We want the label to be
-             aligned with the text above it -->
-        <item name="android:layout_marginStart">-16dp</item>
-    </style>
-
-    <style name="SetupWizardButton.Positive" parent="@style/SuwGlifButton.Primary" />
-
-    <!-- Style for security lock pattern. -->
-    <style name="LockPatternStyle">
-        <item name="*android:regularColor">@color/lock_pattern_regular</item>
-        <item name="*android:successColor">@color/lock_pattern_success</item>
+    <style name="LockPattern">
+        <item name="*android:regularColor">@*android:color/car_body1</item>
+        <item name="*android:successColor">@*android:color/car_blue_500</item>
         <item name="*android:errorColor">?android:attr/colorError</item>
     </style>
 
     <style name="PinPadKey">
-        <item name="android:layout_width">@dimen/pin_pad_key_width</item>
-        <item name="android:layout_height">@dimen/pin_pad_key_height</item>
-        <item name="android:layout_margin">@dimen/pin_pad_key_margin</item>
         <item name="android:gravity">center</item>
         <item name="android:textStyle">normal</item>
-        <item name="android:textSize">@dimen/car_body1_size</item>
-        <item name="android:textColor">@color/car_body3</item>
+        <item name="android:textSize">@*android:dimen/car_body1_size</item>
+        <item name="android:textColor">@*android:color/car_body3</item>
+        <item name="android:tint">@*android:color/car_body3</item>
         <item name="android:clickable">true</item>
-        <item name="android:background">@drawable/car_button_ripple_background</item>
+        <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
     </style>
 
-    <style name="SettingList">
-        <item name="gutter">both</item>
+    <style name="DataUsageSummaryCarrierInfoTextAppearance"
+           parent="@android:style/TextAppearance.DeviceDefault.Small">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:textStyle">normal</item>
+    </style>
+
+    <style name="DataUsageSummaryCarrierInfoWarningTextAppearance"
+           parent="@android:style/TextAppearance.DeviceDefault.Small">
+        <item name="android:textColor">?android:attr/colorError</item>
+        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:textStyle">normal</item>
     </style>
 </resources>
diff --git a/res/values/styles_preference.xml b/res/values/styles_preference.xml
new file mode 100644
index 0000000..6e8918c
--- /dev/null
+++ b/res/values/styles_preference.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<resources>
+    <style name="PreferenceFragment.Settings">
+        <item name="android:layout">@layout/settings_fragment</item>
+        <item name="android:divider">@drawable/divider</item>
+        <item name="allowDividerAfterLastItem">false</item>
+    </style>
+
+    <style name="PreferenceFragmentList.Settings">
+        <item name="android:paddingTop">0dp</item>
+        <item name="android:paddingBottom">0dp</item>
+        <item name="android:paddingStart">0dp</item>
+        <item name="android:paddingEnd">0dp</item>
+        <item name="android:paddingLeft">0dp</item>
+        <item name="android:paddingRight">0dp</item>
+    </style>
+
+    <style name="Preference.Settings" parent="@*android:style/Preference.DeviceDefault">
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+    </style>
+
+    <style name="Preference.Settings.Category"
+           parent="@*android:style/Preference.DeviceDefault.Category">
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+    </style>
+
+    <style name="Preference.Settings.CheckBoxPreference"
+           parent="@*android:style/Preference.DeviceDefault.CheckBoxPreference">
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+    </style>
+
+    <style name="Preference.Settings.DialogPreference"
+           parent="@*android:style/Preference.DeviceDefault.DialogPreference">
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+    </style>
+
+    <style name="Preference.Settings.DialogPreference.EditTextPreference"
+           parent="@*android:style/Preference.DeviceDefault.DialogPreference.EditTextPreference">
+        <item name="dialogLayout">@layout/preference_dialog_edittext</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+    </style>
+
+    <style name="Preference.Settings.SeekBarPreference"
+           parent="@*android:style/Preference.DeviceDefault.SeekBarPreference">
+        <item name="layout">@layout/seekbar_preference</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+    </style>
+
+    <style name="Preference.Settings.SwitchPreference"
+           parent="@*android:style/Preference.DeviceDefault.SwitchPreference">
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+    </style>
+</resources>
diff --git a/res/values/themes.xml b/res/values/themes.xml
new file mode 100644
index 0000000..ac64921
--- /dev/null
+++ b/res/values/themes.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<resources>
+    <style name="CarSettingTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+        <item name="preferenceTheme">@style/PreferenceTheme</item>
+        <item name="wifi_signal">@drawable/wifi_signal</item>
+        <item name="wifiSignalColor">?android:attr/textColorPrimary</item>
+        <item name="iconColor">?android:attr/textColorPrimary</item>
+        <item name="dividerColor">@*android:color/car_list_divider</item>
+        <item name="userSwitcherBackground">?android:attr/colorPrimary</item>
+        <item name="userSwitcherCurrentUserColor">?android:attr/colorAccent</item>
+        <item name="userSwitcherAddIconColor">@*android:color/car_tint</item>
+        <item name="userSwitcherAddIconBackgroundColor">?android:attr/colorPrimaryDark</item>
+        <item name="userSwitcherNameTextAppearance">?android:attr/textAppearanceLarge</item>
+        <item name="quickSettingsEnabledColor">?android:attr/colorControlActivated</item>
+        <item name="quickSettingsDisabledColor">?android:attr/colorControlNormal</item>
+        <item name="quickSettingsIconEnabledColor">?android:attr/colorPrimary</item>
+        <item name="quickSettingsIconDisabledColor">?android:attr/colorPrimaryDark</item>
+        <item name="suggestionsPrimaryColor">?android:attr/colorPrimaryDark</item>
+        <item name="suggestionsSecondaryColor">@android:color/black</item>
+        <item name="android:windowEnterAnimation">@anim/trans_fade_in</item>
+        <item name="android:windowExitAnimation">@anim/trans_fade_out</item>
+        <item name="android:fragmentOpenEnterAnimation">@animator/trans_right_in</item>
+        <item name="android:fragmentOpenExitAnimation">@animator/trans_left_out</item>
+        <item name="android:fragmentCloseEnterAnimation">@animator/trans_left_in</item>
+        <item name="android:fragmentCloseExitAnimation">@animator/trans_right_out</item>
+    </style>
+
+    <style name="PreferenceTheme">
+        <item name="preferenceFragmentCompatStyle">@style/PreferenceFragment.Settings</item>
+        <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Settings</item>
+        <item name="preferenceStyle">@style/Preference.Settings</item>
+        <item name="preferenceCategoryStyle">@style/Preference.Settings.Category</item>
+        <item name="checkBoxPreferenceStyle">@style/Preference.Settings.CheckBoxPreference</item>
+        <item name="dialogPreferenceStyle">@style/Preference.Settings.DialogPreference</item>
+        <item name="editTextPreferenceStyle">@style/Preference.Settings.DialogPreference.EditTextPreference</item>
+        <item name="seekBarPreferenceStyle">@style/Preference.Settings.SeekBarPreference</item>
+        <item name="switchPreferenceStyle">@style/Preference.Settings.SwitchPreference</item>
+    </style>
+
+    <style name="FallbackHome" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowShowWallpaper">true</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowTranslucentStatus">false</item>
+        <item name="android:windowTranslucentNavigation">false</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+        <item name="android:statusBarColor">#00000000</item>
+        <item name="android:navigationBarColor">#00000000</item>
+    </style>
+
+    <!-- Themes for Setup Wizard -->
+
+    <style name="FallbackHome.SetupWizard"
+           parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
+        <item name="android:windowBackground">@android:color/black</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+        <item name="android:statusBarColor">#00000000</item>
+        <item name="android:navigationBarColor">#00000000</item>
+    </style>
+</resources>
diff --git a/res/xml/about_settings_fragment.xml b/res/xml/about_settings_fragment.xml
new file mode 100644
index 0000000..c7c2b20
--- /dev/null
+++ b/res/xml/about_settings_fragment.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/about_settings">
+    <Preference
+        android:key="@string/pk_model_info"
+        android:title="@string/model_info"
+        settings:controller="com.android.car.settings.system.ModelInfoPreferenceController"/>
+    <Preference
+        android:key="@string/pk_firmware_version"
+        android:title="@string/firmware_version"
+        settings:controller="com.android.car.settings.system.FirmwareVersionPreferenceController"/>
+    <Preference
+        android:key="@string/pk_security_patch"
+        android:title="@string/security_patch"
+        settings:controller="com.android.car.settings.system.SecurityPatchPreferenceController"/>
+    <Preference
+        android:key="@string/pk_kernel_version"
+        android:title="@string/kernel_version"
+        settings:controller="com.android.car.settings.system.KernelVersionPreferenceController"/>
+    <Preference
+        android:key="@string/pk_build_number"
+        android:title="@string/build_number"
+        settings:controller="com.android.car.settings.system.BuildNumberPreferenceController"/>
+    <Preference
+        android:key="@string/pk_regulatory_labels"
+        android:title="@string/regulatory_labels"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController">
+        <intent android:action="android.settings.SHOW_REGULATORY_INFO"/>
+    </Preference>
+    <Preference
+        android:key="@string/pk_about_bluetooth_mac_address"
+        android:title="@string/bluetooth_mac_address"
+        settings:controller="com.android.car.settings.system.BluetoothMacAddressPreferenceController"/>
+    <Preference
+        android:key="@string/pk_about_wifi_mac_address"
+        android:title="@string/about_wifi_mac_address"
+        settings:controller="com.android.car.settings.system.WifiMacAddressPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/account_details_fragment.xml b/res/xml/account_details_fragment.xml
new file mode 100644
index 0000000..b6214f0
--- /dev/null
+++ b/res/xml/account_details_fragment.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <Preference
+        android:key="@string/pk_account_details"
+        android:selectable="false"
+        settings:controller="com.android.car.settings.accounts.AccountDetailsPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_account_settings"
+        settings:controller="com.android.car.settings.accounts.AccountDetailsSettingController">
+        <intent android:action="com.android.settings.action.IA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.ia.account_detail"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+    <Preference
+        android:key="@string/pk_account_sync"
+        android:title="@string/account_sync_title"
+        settings:controller="com.android.car.settings.accounts.AccountSyncPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/account_settings_fragment.xml b/res/xml/account_settings_fragment.xml
new file mode 100644
index 0000000..0280760
--- /dev/null
+++ b/res/xml/account_settings_fragment.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/accounts_settings_title">
+    <PreferenceCategory
+        android:key="@string/pk_account_list"
+        android:title="@string/account_list_title"
+        settings:controller="com.android.car.settings.accounts.AccountListPreferenceController"/>
+    <SwitchPreference
+        android:key="@string/pk_account_auto_sync"
+        android:title="@string/account_auto_sync_title"
+        android:summary="@string/account_auto_sync_summary"
+        settings:controller="com.android.car.settings.accounts.AccountAutoSyncPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_accounts_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.ia.accounts"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/account_sync_details_fragment.xml b/res/xml/account_sync_details_fragment.xml
new file mode 100644
index 0000000..60e3244
--- /dev/null
+++ b/res/xml/account_sync_details_fragment.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/account_sync_title">
+    <Preference
+        android:key="@string/pk_account_details_with_sync"
+        android:selectable="false"
+        settings:controller="com.android.car.settings.accounts.AccountDetailsWithSyncStatusPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_account_sync_details"
+        settings:controller="com.android.car.settings.accounts.AccountSyncDetailsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/add_wifi_fragment.xml b/res/xml/add_wifi_fragment.xml
new file mode 100644
index 0000000..d0eafa3
--- /dev/null
+++ b/res/xml/add_wifi_fragment.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/wifi_setup_add_network">
+    <EditTextPreference
+        android:key="@string/pk_add_wifi_network_name"
+        android:persistent="false"
+        android:title="@string/wifi_ssid"
+        settings:controller="com.android.car.settings.wifi.NetworkNamePreferenceController"/>
+    <ListPreference
+        android:dialogTitle="@string/wifi_security"
+        android:key="@string/pk_add_wifi_security"
+        android:persistent="false"
+        android:title="@string/wifi_security"
+        settings:controller="com.android.car.settings.wifi.NetworkSecurityPreferenceController"/>
+    <com.android.car.settings.wifi.NetworkNameRestrictedPasswordEditTextPreference
+        android:dialogTitle="@string/wifi_password"
+        android:key="@string/pk_add_wifi_password"
+        android:persistent="false"
+        android:summary="@string/default_password_summary"
+        android:title="@string/wifi_password"
+        settings:controller="com.android.car.settings.wifi.NetworkPasswordPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/app_data_usage_fragment.xml b/res/xml/app_data_usage_fragment.xml
new file mode 100644
index 0000000..647b7d4
--- /dev/null
+++ b/res/xml/app_data_usage_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/app_data_usage">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_app_data_usage_detail"
+        settings:controller="com.android.car.settings.datausage.AppDataUsagePreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/app_storage_settings_details_fragment.xml b/res/xml/app_storage_settings_details_fragment.xml
new file mode 100644
index 0000000..b9eeb41
--- /dev/null
+++ b/res/xml/app_storage_settings_details_fragment.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/storage_settings_title">
+    <Preference
+        android:key="@string/pk_storage_application_details"
+        settings:controller="com.android.car.settings.storage.StorageApplicationPreferenceController"/>
+    <com.android.car.settings.storage.StorageAppDetailPreference
+        android:key="@string/pk_storage_application_size"
+        android:selectable="false"
+        android:title="@string/storage_application_size_label"
+        settings:controller="com.android.car.settings.storage.StorageApplicationSizePreferenceController"/>
+    <com.android.car.settings.storage.StorageAppDetailPreference
+        android:key="@string/pk_storage_application_data_size"
+        android:selectable="false"
+        android:title="@string/storage_data_size_label"
+        settings:controller="com.android.car.settings.storage.StorageApplicationUserDataPreferenceController"/>
+    <com.android.car.settings.storage.StorageAppDetailPreference
+        android:key="@string/pk_storage_application_cache_size"
+        android:selectable="false"
+        android:title="@string/storage_cache_size_label"
+        settings:controller="com.android.car.settings.storage.StorageApplicationCacheSizePreferenceController"/>
+    <com.android.car.settings.storage.StorageAppDetailPreference
+        android:key="@string/pk_storage_application_total_size"
+        android:selectable="false"
+        android:title="@string/storage_total_size_label"
+        settings:controller="com.android.car.settings.storage.StorageApplicationTotalSizePreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/application_details_fragment.xml b/res/xml/application_details_fragment.xml
new file mode 100644
index 0000000..2f4ac1a
--- /dev/null
+++ b/res/xml/application_details_fragment.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/applications_settings">
+    <Preference
+        android:key="@string/pk_application_details_app"
+        settings:controller="com.android.car.settings.applications.ApplicationPreferenceController"/>
+    <SwitchPreference
+        android:key="@string/pk_application_details_notifications"
+        android:title="@string/notifications_label"
+        settings:controller="com.android.car.settings.applications.NotificationsPreferenceController"/>
+    <Preference
+        android:key="@string/pk_application_details_permissions"
+        android:title="@string/permissions_label"
+        settings:controller="com.android.car.settings.applications.PermissionsPreferenceController"/>
+    <Preference
+        android:key="@string/pk_application_details_version"
+        settings:controller="com.android.car.settings.applications.VersionPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/application_launch_settings_fragment.xml b/res/xml/application_launch_settings_fragment.xml
new file mode 100644
index 0000000..d5e0dcd
--- /dev/null
+++ b/res/xml/application_launch_settings_fragment.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/app_launch_title">
+    <Preference
+        android:key="@string/pk_opening_links_app_details"
+        settings:controller="com.android.car.settings.applications.managedomainurls.ApplicationWithVersionPreferenceController"/>
+    <PreferenceCategory android:title="@string/app_launch_domain_links_title">
+        <ListPreference
+            android:key="@string/pk_opening_links_app_details_state"
+            android:title="@string/app_launch_open_domain_urls_title"
+            android:summary="%s"
+            settings:controller="com.android.car.settings.applications.managedomainurls.AppLinkStatePreferenceController"/>
+        <Preference
+            android:key="@string/pk_opening_links_app_details_urls"
+            android:title="@string/app_launch_supported_domain_urls_title"
+            android:dependency="@string/pk_opening_links_app_details_state"
+            settings:controller="com.android.car.settings.applications.managedomainurls.DomainUrlsPreferenceController"/>
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/app_launch_other_defaults_title">
+        <Preference
+            android:key="@string/pk_opening_links_app_details_reset"
+            settings:controller="com.android.car.settings.applications.managedomainurls.ClearDefaultsPreferenceController"/>
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/res/xml/applications_settings_fragment.xml b/res/xml/applications_settings_fragment.xml
new file mode 100644
index 0000000..46d7a82
--- /dev/null
+++ b/res/xml/applications_settings_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/applications_settings">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_all_applications_settings_list"
+        settings:controller="com.android.car.settings.applications.ApplicationsSettingsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/apps_and_notifications_fragment.xml b/res/xml/apps_and_notifications_fragment.xml
new file mode 100644
index 0000000..42f8aea
--- /dev/null
+++ b/res/xml/apps_and_notifications_fragment.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/apps_and_notifications_settings">
+    <Preference
+        android:fragment="com.android.car.settings.applications.ApplicationsSettingsFragment"
+        android:key="@string/pk_applications_settings_screen_entry"
+        android:title="@string/all_applications"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.applications.DefaultApplicationsSettingsFragment"
+        android:key="@string/pk_default_applications_settings_entry"
+        android:title="@string/default_applications"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:key="@string/pk_app_permissions_entry"
+        android:title="@string/app_permissions"
+        settings:controller="com.android.car.settings.applications.AppPermissionsEntryPreferenceController">
+        <intent android:action="android.intent.action.MANAGE_PERMISSIONS"/>
+    </Preference>
+    <Preference
+        android:fragment="com.android.car.settings.applications.specialaccess.SpecialAccessSettingsFragment"
+        android:key="@string/pk_special_access_entry"
+        android:title="@string/special_access"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml
new file mode 100644
index 0000000..e6f0483
--- /dev/null
+++ b/res/xml/bluetooth_device_details_fragment.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/bluetooth_settings_title">
+    <Preference
+        android:key="@string/pk_bluetooth_device_name"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothDeviceNamePreferenceController"/>
+    <PreferenceCategory
+        android:key="@string/pk_bluetooth_device_profiles"
+        android:title="@string/bluetooth_profiles"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothDeviceProfilesPreferenceController"/>
+    <Preference
+        android:icon="@drawable/ic_settings_about"
+        android:key="@string/pk_bluetooth_device_address"
+        android:selectable="false"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothDeviceAddressPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/bluetooth_device_picker_fragment.xml b/res/xml/bluetooth_device_picker_fragment.xml
new file mode 100644
index 0000000..95dcacd
--- /dev/null
+++ b/res/xml/bluetooth_device_picker_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/bluetooth_device_picker">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_bluetooth_device_picker"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothDevicePickerPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/bluetooth_pairing_selection_fragment.xml b/res/xml/bluetooth_pairing_selection_fragment.xml
new file mode 100644
index 0000000..4613c53
--- /dev/null
+++ b/res/xml/bluetooth_pairing_selection_fragment.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/bluetooth_pair_new_device">
+    <Preference
+        android:key="@string/pk_bluetooth_name"
+        android:title="@string/bluetooth_name"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothNamePreferenceController"/>
+    <PreferenceCategory
+        android:key="@string/pk_bluetooth_available_devices"
+        android:title="@string/bluetooth_available_devices"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothUnbondedDevicesPreferenceController"/>
+    <Preference
+        android:icon="@drawable/ic_settings_about"
+        android:key="@string/pk_bluetooth_address"
+        android:selectable="false"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothAddressPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/bluetooth_settings_fragment.xml b/res/xml/bluetooth_settings_fragment.xml
new file mode 100644
index 0000000..ccd041b
--- /dev/null
+++ b/res/xml/bluetooth_settings_fragment.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/bluetooth_settings_title">
+    <PreferenceCategory
+        android:key="@string/pk_bluetooth_paired_devices"
+        android:title="@string/bluetooth_paired_devices"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothBondedDevicesPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.bluetooth.BluetoothPairingSelectionFragment"
+        android:icon="@drawable/ic_add"
+        android:key="@string/pk_bluetooth_pair_new_device"
+        android:title="@string/bluetooth_pair_new_device"
+        settings:controller="com.android.car.settings.bluetooth.PairNewDevicePreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/car_volume_items.xml b/res/xml/car_volume_items.xml
index 08ab4f9..e8a5683 100644
--- a/res/xml/car_volume_items.xml
+++ b/res/xml/car_volume_items.xml
@@ -1,20 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 2018, 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.
-*/
+    Copyright 2018 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.
 -->
 
 <!--
@@ -32,52 +30,68 @@
   car_volume_groups.xml, which is read by car audio service.
 -->
 <carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
-    <item car:usage="voice_communication"
-          car:title="@*android:string/volume_call"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="voice_communication_signalling"
-          car:title="@*android:string/volume_call"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="media"
-          car:title="@*android:string/volume_music"
-          car:icon="@*android:drawable/ic_audio_media"/>
-    <item car:usage="game"
-          car:title="@*android:string/volume_music"
-          car:icon="@*android:drawable/ic_audio_media"/>
-    <item car:usage="alarm"
-          car:title="@*android:string/volume_alarm"
-          car:icon="@*android:drawable/ic_audio_alarm"/>
-    <item car:usage="assistance_navigation_guidance"
-          car:title="@string/navi_volume_title"
-          car:icon="@drawable/ic_audio_navi"/>
-    <item car:usage="notification_ringtone"
-          car:title="@*android:string/volume_ringtone"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="assistant"
-          car:title="@*android:string/volume_unknown"
-          car:icon="@*android:drawable/ic_audio_vol"/>
-    <item car:usage="notification"
-          car:title="@*android:string/volume_notification"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="notification_communication_request"
-          car:title="@*android:string/volume_notification"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="notification_communication_instant"
-          car:title="@*android:string/volume_notification"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="notification_communication_delayed"
-          car:title="@*android:string/volume_notification"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="notification_event"
-          car:title="@*android:string/volume_notification"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="assistance_accessibility"
-          car:title="@*android:string/volume_notification"
-          car:icon="@*android:drawable/ic_audio_ring_notif"/>
-    <item car:usage="assistance_sonification"
-          car:title="@*android:string/volume_unknown"
-          car:icon="@*android:drawable/ic_audio_vol"/>
-    <item car:usage="unknown"
-          car:title="@*android:string/volume_unknown"
-          car:icon="@*android:drawable/ic_audio_vol"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_call"
+        car:usage="voice_communication"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_call"
+        car:usage="voice_communication_signalling"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_media"
+        car:titleText="@*android:string/volume_music"
+        car:usage="media"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_media"
+        car:titleText="@*android:string/volume_music"
+        car:usage="game"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_alarm"
+        car:titleText="@*android:string/volume_alarm"
+        car:usage="alarm"/>
+    <item
+        car:icon="@drawable/ic_audio_navi"
+        car:titleText="@string/navi_volume_title"
+        car:usage="assistance_navigation_guidance"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_ringtone"
+        car:usage="notification_ringtone"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_vol"
+        car:titleText="@*android:string/volume_unknown"
+        car:usage="assistant"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_notification"
+        car:usage="notification"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_notification"
+        car:usage="notification_communication_request"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_notification"
+        car:usage="notification_communication_instant"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_notification"
+        car:usage="notification_communication_delayed"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_notification"
+        car:usage="notification_event"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_ring_notif"
+        car:titleText="@*android:string/volume_notification"
+        car:usage="assistance_accessibility"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_vol"
+        car:titleText="@*android:string/volume_unknown"
+        car:usage="assistance_sonification"/>
+    <item
+        car:icon="@*android:drawable/ic_audio_vol"
+        car:titleText="@*android:string/volume_unknown"
+        car:usage="unknown"/>
 </carVolumeItems>
diff --git a/res/xml/child_locale_picker_fragment.xml b/res/xml/child_locale_picker_fragment.xml
new file mode 100644
index 0000000..43a727e
--- /dev/null
+++ b/res/xml/child_locale_picker_fragment.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_child_locale_picker"
+        settings:controller="com.android.car.settings.language.ChildLocalePickerPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/choose_account_fragment.xml b/res/xml/choose_account_fragment.xml
new file mode 100644
index 0000000..e154bd3
--- /dev/null
+++ b/res/xml/choose_account_fragment.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/add_an_account">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_add_account"
+        settings:controller="com.android.car.settings.accounts.ChooseAccountPreferenceController"/>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/choose_lock_type_fragment.xml b/res/xml/choose_lock_type_fragment.xml
new file mode 100644
index 0000000..a4a10a1
--- /dev/null
+++ b/res/xml/choose_lock_type_fragment.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/lock_settings_picker_title">
+    <Preference
+        android:key="@string/pk_no_lock"
+        android:title="@string/security_lock_none"
+        settings:controller="com.android.car.settings.security.NoLockPreferenceController"/>
+    <Preference
+        android:key="@string/pk_pattern_lock"
+        android:title="@string/security_lock_pattern"
+        settings:controller="com.android.car.settings.security.PatternLockPreferenceController"/>
+    <Preference
+        android:key="@string/pk_password_lock"
+        android:title="@string/security_lock_password"
+        settings:controller="com.android.car.settings.security.PasswordLockPreferenceController"/>
+    <Preference
+        android:key="@string/pk_pin_lock"
+        android:title="@string/security_lock_pin"
+        settings:controller="com.android.car.settings.security.PinLockPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/choose_new_admin_fragment.xml b/res/xml/choose_new_admin_fragment.xml
new file mode 100644
index 0000000..04a1d0c
--- /dev/null
+++ b/res/xml/choose_new_admin_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/choose_new_admin_label">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_choose_new_admin"
+        settings:controller="com.android.car.settings.users.ChooseNewAdminPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/data_usage_fragment.xml b/res/xml/data_usage_fragment.xml
new file mode 100644
index 0000000..fcb5590
--- /dev/null
+++ b/res/xml/data_usage_fragment.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/data_usage_settings">
+    <com.android.car.settings.datausage.DataUsageSummaryPreference
+        android:key="@string/pk_data_usage_summary"
+        settings:controller="com.android.car.settings.datausage.DataUsageSummaryPreferenceController"
+        settings:singleLineTitle="false"/>
+    <Preference
+        android:key="@string/pk_app_data_usage"
+        android:title="@string/app_data_usage"
+        settings:controller="com.android.car.settings.datausage.DataUsagePreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.datausage.DataWarningAndLimitFragment"
+        android:key="@string/pk_data_warning_and_limit"
+        android:title="@string/data_warning_limit_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/data_warning_and_limit_fragment.xml b/res/xml/data_warning_and_limit_fragment.xml
new file mode 100644
index 0000000..8933d6c
--- /dev/null
+++ b/res/xml/data_warning_and_limit_fragment.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/data_warning_limit_title">
+    <Preference
+        android:key="@string/pk_data_usage_cycle"
+        android:title="@string/app_usage_cycle"
+        settings:controller="com.android.car.settings.datausage.CycleResetDayOfMonthPickerPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_data_warning_group"
+        settings:controller="com.android.car.settings.datausage.DataWarningPreferenceController">
+        <SwitchPreference
+            android:key="@string/pk_data_set_warning"
+            android:title="@string/set_data_warning"/>
+        <Preference
+            android:key="@string/pk_data_warning"
+            android:title="@string/data_warning"/>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_data_limit_group"
+        settings:controller="com.android.car.settings.datausage.DataLimitPreferenceController">
+        <SwitchPreference
+            android:key="@string/pk_data_set_limit"
+            android:title="@string/set_data_limit"/>
+        <Preference
+            android:key="@string/pk_data_limit"
+            android:title="@string/data_limit"/>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/datetime_settings_fragment.xml b/res/xml/datetime_settings_fragment.xml
new file mode 100644
index 0000000..6a4902e
--- /dev/null
+++ b/res/xml/datetime_settings_fragment.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/date_and_time_settings_title">
+    <SwitchPreference
+        android:key="@string/pk_auto_datetime_switch"
+        android:summary="@string/date_time_auto_summary"
+        android:title="@string/date_time_auto"
+        settings:controller="com.android.car.settings.datetime.AutoDatetimeTogglePreferenceController"/>
+    <SwitchPreference
+        android:key="@string/pk_auto_timezone_switch"
+        android:summary="@string/zone_auto_summary"
+        android:title="@string/zone_auto"
+        settings:controller="com.android.car.settings.datetime.AutoTimeZoneTogglePreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.datetime.DatePickerFragment"
+        android:key="@string/pk_date_picker_entry"
+        android:title="@string/date_time_set_date"
+        settings:controller="com.android.car.settings.datetime.DatePickerPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.datetime.TimePickerFragment"
+        android:key="@string/pk_time_picker_entry"
+        android:title="@string/date_time_set_time"
+        settings:controller="com.android.car.settings.datetime.TimePickerPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.datetime.TimeZonePickerScreenFragment"
+        android:key="@string/pk_timezone_picker_screen_entry"
+        android:title="@string/date_time_set_timezone"
+        settings:controller="com.android.car.settings.datetime.TimeZonePickerPreferenceController"/>
+    <SwitchPreference
+        android:key="@string/pk_use_24hour_switch"
+        android:title="@string/date_time_24hour"
+        settings:controller="com.android.car.settings.datetime.TimeFormatTogglePreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/default_applications_settings_fragment.xml b/res/xml/default_applications_settings_fragment.xml
new file mode 100644
index 0000000..4282fc4
--- /dev/null
+++ b/res/xml/default_applications_settings_fragment.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/default_applications">
+    <Preference
+        android:fragment="com.android.car.settings.applications.assist.ManageAssistFragment"
+        android:key="@string/pk_default_assist_and_voice"
+        android:title="@string/assist_and_voice_input_settings"
+        settings:controller="com.android.car.settings.applications.assist.ManageAssistEntryPreferenceController"
+        settings:iconSpaceReserved="true"/>
+    <Preference
+        android:fragment="com.android.car.settings.applications.managedomainurls.ManageDomainUrlsFragment"
+        android:key="@string/pk_opening_links_entry"
+        android:title="@string/app_launch_domain_links_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"
+        settings:iconSpaceReserved="true"/>
+</PreferenceScreen>
diff --git a/res/xml/default_assistant_picker_fragment.xml b/res/xml/default_assistant_picker_fragment.xml
new file mode 100644
index 0000000..27545a9
--- /dev/null
+++ b/res/xml/default_assistant_picker_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/assist_app_settings">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_default_assist_and_voice_options"
+        settings:controller="com.android.car.settings.applications.defaultapps.DefaultAssistantPickerPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/default_autofill_picker_fragment.xml b/res/xml/default_autofill_picker_fragment.xml
new file mode 100644
index 0000000..e465461
--- /dev/null
+++ b/res/xml/default_autofill_picker_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/autofill_settings_title">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_default_autofill_options"
+        settings:controller="com.android.car.settings.applications.defaultapps.DefaultAutofillPickerPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/default_voice_input_picker_fragment.xml b/res/xml/default_voice_input_picker_fragment.xml
new file mode 100644
index 0000000..042d5be
--- /dev/null
+++ b/res/xml/default_voice_input_picker_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/voice_input_settings_title">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_default_voice_input_options"
+        settings:controller="com.android.car.settings.applications.assist.DefaultVoiceInputPickerPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/developer_options_fragment.xml b/res/xml/developer_options_fragment.xml
new file mode 100644
index 0000000..ebe578b
--- /dev/null
+++ b/res/xml/developer_options_fragment.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/developer_options_settings">
+    <PreferenceCategory
+        android:title="@string/debug_debugging_category">
+        <SwitchPreference
+            android:key="@string/pk_usb_debugging_toggle"
+            android:title="@string/enable_adb"
+            android:summary="@string/enable_adb_summary"
+            settings:controller="com.android.car.settings.development.debugging.EnableAdbPreferenceController"/>
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/res/xml/directory_access_details_fragment.xml b/res/xml/directory_access_details_fragment.xml
new file mode 100644
index 0000000..f773dd0
--- /dev/null
+++ b/res/xml/directory_access_details_fragment.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/directory_access_title">
+    <Preference
+        android:key="@string/pk_directory_access_details_app"
+        android:selectable="false"
+        settings:controller="com.android.car.settings.applications.ApplicationPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_directory_access_details"
+        settings:controller="com.android.car.settings.applications.specialaccess.DirectoryAccessDetailsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/directory_access_fragment.xml b/res/xml/directory_access_fragment.xml
new file mode 100644
index 0000000..ff5d2cb
--- /dev/null
+++ b/res/xml/directory_access_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/directory_access_title">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_directory_access"
+        settings:controller="com.android.car.settings.applications.specialaccess.DirectoryAccessPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/display_settings_fragment.xml b/res/xml/display_settings_fragment.xml
new file mode 100644
index 0000000..7932f89
--- /dev/null
+++ b/res/xml/display_settings_fragment.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/display_settings">
+    <SwitchPreference
+        android:key="@string/pk_adaptive_brightness_switch"
+        android:summary="@string/auto_brightness_summary"
+        android:title="@string/auto_brightness_title"
+        settings:controller="com.android.car.settings.display.AdaptiveBrightnessTogglePreferenceController"/>
+    <com.android.car.settings.common.SeekBarPreference
+        android:key="@string/pk_brightness_level"
+        android:title="@string/brightness"
+        settings:controller="com.android.car.settings.display.BrightnessLevelPreferenceController"
+        settings:showSeekBarValue="false"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_display_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.ia.display"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/file_paths.xml b/res/xml/file_paths.xml
new file mode 100644
index 0000000..7971793
--- /dev/null
+++ b/res/xml/file_paths.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<paths>
+    <!-- Offer access to files under Context.getCacheDir() -->
+    <cache-path name="my_cache"/>
+</paths>
diff --git a/res/xml/homepage_fragment.xml b/res/xml/homepage_fragment.xml
new file mode 100644
index 0000000..10e3e2d
--- /dev/null
+++ b/res/xml/homepage_fragment.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/settings_label">
+    <!-- TODO: Re-enable once more suggestion use cases are supported.
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_suggestions"
+        settings:controller="com.android.car.settings.suggestions.SuggestionsPreferenceController"/>
+    -->
+    <Preference
+        android:fragment="com.android.car.settings.display.DisplaySettingsFragment"
+        android:icon="@drawable/ic_settings_display"
+        android:key="@string/pk_display_settings_entry"
+        android:title="@string/display_settings"/>
+    <Preference
+        android:fragment="com.android.car.settings.sound.SoundSettingsFragment"
+        android:icon="@drawable/ic_settings_sound"
+        android:key="@string/pk_sound_settings_entry"
+        android:title="@string/sound_settings"/>
+    <Preference
+        android:fragment="com.android.car.settings.network.NetworkAndInternetFragment"
+        android:icon="@drawable/ic_settings_wifi"
+        android:key="@string/pk_network_and_internet_entry"
+        android:title="@string/network_and_internet"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_wireless_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.wireless"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+    <Preference
+        android:fragment="com.android.car.settings.bluetooth.BluetoothSettingsFragment"
+        android:icon="@drawable/ic_settings_bluetooth"
+        android:key="@string/pk_bluetooth_settings_entry"
+        android:title="@string/bluetooth_settings_title"
+        settings:controller="com.android.car.settings.bluetooth.BluetoothEntryPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.location.LocationSettingsFragment"
+        android:icon="@drawable/ic_settings_location"
+        android:key="@string/pk_location_settings_entry"
+        android:title="@string/location_settings_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.applications.AppsAndNotificationsFragment"
+        android:icon="@drawable/ic_settings_applications"
+        android:key="@string/pk_apps_and_notifications_settings_entry"
+        android:title="@string/apps_and_notifications_settings"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.datetime.DatetimeSettingsFragment"
+        android:icon="@drawable/ic_settings_date_time"
+        android:key="@string/pk_date_time_settings_entry"
+        android:title="@string/date_and_time_settings_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:icon="@drawable/ic_user"
+        android:key="@string/pk_users_settings_entry"
+        android:title="@string/users_list_title"
+        settings:controller="com.android.car.settings.users.UsersEntryPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.accounts.AccountSettingsFragment"
+        android:icon="@drawable/ic_account"
+        android:key="@string/pk_accounts_settings_entry"
+        android:title="@string/accounts_settings_title"
+        settings:controller="com.android.car.settings.accounts.AccountsEntryPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.storage.StorageSettingsFragment"
+        android:icon="@drawable/ic_storage"
+        android:key="@string/pk_storage_settings_entry"
+        android:title="@string/storage_settings_title"/>
+    <Preference
+        android:icon="@drawable/ic_lock"
+        android:key="@string/pk_security_settings_entry"
+        android:title="@string/security_settings_title"
+        settings:controller="com.android.car.settings.security.SecurityEntryPreferenceController">
+        <intent
+            android:targetClass="com.android.car.settings.security.SettingsScreenLockActivity"
+            android:targetPackage="com.android.car.settings"/>
+    </Preference>
+    <Preference
+        android:fragment="com.android.car.settings.system.SystemSettingsFragment"
+        android:icon="@drawable/ic_settings_about"
+        android:key="@string/pk_system_settings_entry"
+        android:title="@string/system_setting_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_device_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.ia.device"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_personal_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.personal"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/keyboard_fragment.xml b/res/xml/keyboard_fragment.xml
new file mode 100644
index 0000000..0ba6b25
--- /dev/null
+++ b/res/xml/keyboard_fragment.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/keyboard_settings">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_enabled_keyboards"
+        settings:controller="com.android.car.settings.inputmethod.EnabledKeyboardPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.inputmethod.KeyboardManagementFragment"
+        android:icon="@drawable/ic_add"
+        android:key="@string/pk_manage_keyboard"
+        android:title="@string/manage_keyboard"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/keyboard_management_fragment.xml b/res/xml/keyboard_management_fragment.xml
new file mode 100644
index 0000000..e82ebe1
--- /dev/null
+++ b/res/xml/keyboard_management_fragment.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/manage_keyboard">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_keyboard_management"
+        settings:controller="com.android.car.settings.inputmethod.KeyboardManagementPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/language_picker_fragment.xml b/res/xml/language_picker_fragment.xml
new file mode 100644
index 0000000..64978bb
--- /dev/null
+++ b/res/xml/language_picker_fragment.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/language_settings">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_language_picker"
+        settings:controller="com.android.car.settings.language.LanguagePickerPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/languages_and_input_fragment.xml b/res/xml/languages_and_input_fragment.xml
new file mode 100644
index 0000000..424eceb
--- /dev/null
+++ b/res/xml/languages_and_input_fragment.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/languages_and_input_settings">
+    <Preference
+        android:fragment="com.android.car.settings.language.LanguagePickerFragment"
+        android:icon="@drawable/ic_translate"
+        android:key="@string/pk_language_settings_entry"
+        android:title="@string/language_settings"
+        settings:controller="com.android.car.settings.language.LanguageSettingsEntryPreferenceController"/>
+    <com.android.car.settings.common.ButtonPreference
+        android:fragment="com.android.car.settings.applications.defaultapps.DefaultAutofillPickerFragment"
+        android:key="@string/pk_autofill_picker_entry"
+        android:summary="@string/app_list_preference_none"
+        android:title="@string/autofill_settings_title"
+        android:widgetLayout="@layout/details_preference_widget"
+        settings:controller="com.android.car.settings.applications.defaultapps.DefaultAutofillPickerEntryPreferenceController"
+        settings:iconSpaceReserved="true"/>
+    <Preference
+        android:fragment="com.android.car.settings.inputmethod.KeyboardFragment"
+        android:key="@string/pk_keyboard_entry"
+        android:title="@string/keyboard_settings"
+        settings:controller="com.android.car.settings.inputmethod.KeyboardPreferenceController"
+        settings:iconSpaceReserved="true"/>
+    <Preference
+        android:fragment="com.android.car.settings.tts.TextToSpeechOutputFragment"
+        android:key="@string/pk_tts_settings_entry"
+        android:title="@string/text_to_speech_settings"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"
+        settings:iconSpaceReserved="true"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_language_and_input_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.ia.language"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/legal_information_fragment.xml b/res/xml/legal_information_fragment.xml
new file mode 100644
index 0000000..e7e343f
--- /dev/null
+++ b/res/xml/legal_information_fragment.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/legal_information">
+
+    <!-- Terms and conditions -->
+    <Preference
+        android:key="terms"
+        android:title="@string/terms_title"
+        settings:controller="com.android.car.settings.system.legal.TermsPreferenceController"/>
+
+    <!-- System WebView License information -->
+    <Preference
+        android:key="@string/pk_system_license_entry"
+        android:title="@string/webview_license_title"
+        settings:controller="com.android.car.settings.system.legal.WebViewLicensePreferenceController"/>
+
+    <Preference
+        android:key="@string/pk_third_party_license_entry"
+        android:title="@string/settings_license_activity_title"
+        settings:controller="com.android.car.settings.system.legal.ThirdPartyLicensePreferenceController"/>
+
+</PreferenceScreen>
diff --git a/res/xml/location_recent_requests_fragment.xml b/res/xml/location_recent_requests_fragment.xml
new file mode 100644
index 0000000..982cd63
--- /dev/null
+++ b/res/xml/location_recent_requests_fragment.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:key="@string/pk_location_recent_requests_screen"
+                  android:title="@string/location_settings_recent_requests_title"
+                  settings:controller="com.android.car.settings.location.RecentLocationRequestsPreferenceController"/>
\ No newline at end of file
diff --git a/res/xml/location_scanning_fragment.xml b/res/xml/location_scanning_fragment.xml
new file mode 100644
index 0000000..f71f246
--- /dev/null
+++ b/res/xml/location_scanning_fragment.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/location_settings_scanning_title">
+    <SwitchPreference
+        android:key="@string/pk_location_scanning_wifi"
+        android:summary="@string/location_settings_wifi_scanning_summary"
+        android:title="@string/location_settings_wifi_scanning_title"
+        settings:controller="com.android.car.settings.location.WifiScanningPreferenceController"/>
+    <SwitchPreference
+        android:key="@string/pk_location_scanning_bluetooth"
+        android:summary="@string/location_settings_bluetooth_scanning_summary"
+        android:title="@string/location_settings_bluetooth_scanning_title"
+        settings:controller="com.android.car.settings.location.BluetoothScanningPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/location_settings_fragment.xml b/res/xml/location_settings_fragment.xml
new file mode 100644
index 0000000..4f1b57c
--- /dev/null
+++ b/res/xml/location_settings_fragment.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/location_settings_title">
+    <Preference
+        android:fragment="com.android.car.settings.location.RecentLocationRequestsFragment"
+        android:key="@string/pk_location_recent_requests_entry"
+        android:title="@string/location_settings_recent_requests_title"
+        settings:controller="com.android.car.settings.location.RecentLocationRequestsEntryPreferenceController"/>
+    <Preference
+        android:key="@string/pk_location_app_permissions"
+        android:title="@string/location_settings_app_permissions_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController">
+        <intent android:action="android.intent.action.MANAGE_PERMISSION_APPS">
+            <extra android:name="android.intent.extra.PERMISSION_NAME"
+                   android:value="android.permission-group.LOCATION"/>
+        </intent>
+    </Preference>
+    <Preference
+        android:fragment="com.android.car.settings.location.LocationScanningFragment"
+        android:key="@string/pk_location_scanning"
+        android:title="@string/location_settings_scanning_title"
+        settings:controller="com.android.car.settings.location.LocationScanningPreferenceController"/>
+    <PreferenceCategory
+        android:key="@string/pk_location_services"
+        android:title="@string/location_settings_services_title"
+        settings:controller="com.android.car.settings.location.LocationServicesPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_location_footer"
+        settings:controller="com.android.car.settings.location.LocationFooterPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/manage_assist_fragment.xml b/res/xml/manage_assist_fragment.xml
new file mode 100644
index 0000000..0f682bc
--- /dev/null
+++ b/res/xml/manage_assist_fragment.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/assist_and_voice_input_settings">
+    <com.android.car.settings.common.ButtonPreference
+        android:fragment="com.android.car.settings.applications.defaultapps.DefaultAssistantPickerFragment"
+        android:key="@string/pk_default_assist"
+        android:title="@string/assist_app_settings"
+        android:widgetLayout="@layout/details_preference_widget"
+        settings:controller="com.android.car.settings.applications.defaultapps.DefaultAssistantPickerEntryPreferenceController"
+        settings:iconSpaceReserved="true"/>
+    <SwitchPreference
+        android:key="@string/pk_assist_use_text_context"
+        android:summary="@string/assist_access_context_summary"
+        android:title="@string/assist_access_context_title"
+        settings:controller="com.android.car.settings.applications.assist.TextContextPreferenceController"
+        settings:iconSpaceReserved="true"/>
+    <SwitchPreference
+        android:key="@string/pk_assist_use_screenshot"
+        android:summary="@string/assist_access_screenshot_summary"
+        android:title="@string/assist_access_screenshot_title"
+        settings:controller="com.android.car.settings.applications.assist.ScreenshotContextPreferenceController"
+        settings:iconSpaceReserved="true"/>
+    <com.android.car.settings.common.ButtonPreference
+        android:fragment="com.android.car.settings.applications.assist.DefaultVoiceInputPickerFragment"
+        android:key="@string/pk_default_voice_input"
+        android:title="@string/voice_input_settings_title"
+        android:widgetLayout="@layout/details_preference_widget"
+        settings:controller="com.android.car.settings.applications.assist.DefaultVoiceInputPickerEntryPreferenceController"
+        settings:iconSpaceReserved="true"/>
+</PreferenceScreen>
diff --git a/res/xml/manage_domain_urls_fragment.xml b/res/xml/manage_domain_urls_fragment.xml
new file mode 100644
index 0000000..525fdd5
--- /dev/null
+++ b/res/xml/manage_domain_urls_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/app_launch_domain_links_title">
+    <PreferenceCategory
+        android:key="@string/pk_opening_links_options"
+        android:title="@string/domain_url_section_title"
+        settings:controller="com.android.car.settings.applications.managedomainurls.DomainAppPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/master_clear_confirm_fragment.xml b/res/xml/master_clear_confirm_fragment.xml
new file mode 100644
index 0000000..0ed99ff
--- /dev/null
+++ b/res/xml/master_clear_confirm_fragment.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/master_clear_confirm_title">
+    <Preference
+        android:key="@string/pk_master_clear_confirm_desc"
+        android:selectable="false"
+        android:title="@string/master_clear_confirm_desc"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"
+        settings:singleLineTitle="false"/>
+</PreferenceScreen>
diff --git a/res/xml/master_clear_fragment.xml b/res/xml/master_clear_fragment.xml
new file mode 100644
index 0000000..28375be
--- /dev/null
+++ b/res/xml/master_clear_fragment.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/master_clear_title">
+    <Preference
+        android:key="@string/pk_master_clear_desc"
+        android:selectable="false"
+        android:title="@string/master_clear_desc"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"
+        settings:singleLineTitle="false"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_master_clear_account_list"
+        android:selectable="false"
+        android:title="@string/master_clear_accounts"
+        settings:controller="com.android.car.settings.system.MasterClearAccountsPreferenceController"/>
+    <Preference
+        android:key="@string/pk_master_clear_other_users_present"
+        android:selectable="false"
+        android:title="@string/master_clear_other_users_present"
+        settings:controller="com.android.car.settings.system.MasterClearOtherUsersPresentPreferenceController"
+        settings:singleLineTitle="false"/>
+    <CheckBoxPreference
+        android:defaultValue="true"
+        android:key="@string/pk_master_clear_reset_esim"
+        android:summary="@string/reset_esim_desc"
+        android:title="@string/reset_esim_title"
+        settings:controller="com.android.car.settings.system.MasterClearResetEsimPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/mobile_network_fragment.xml b/res/xml/mobile_network_fragment.xml
new file mode 100644
index 0000000..675ecb0
--- /dev/null
+++ b/res/xml/mobile_network_fragment.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/mobile_network_settings">
+    <SwitchPreference
+        android:key="@string/pk_mobile_data_toggle"
+        android:summary="@string/mobile_network_toggle_summary"
+        android:title="@string/mobile_network_toggle_title"
+        settings:controller="com.android.car.settings.network.MobileDataTogglePreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/modify_system_settings_fragment.xml b/res/xml/modify_system_settings_fragment.xml
new file mode 100644
index 0000000..7aa22b4
--- /dev/null
+++ b/res/xml/modify_system_settings_fragment.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/modify_system_settings_title">
+    <Preference
+        android:key="@string/pk_modify_system_settings_description"
+        android:selectable="false"
+        android:summary="@string/modify_system_settings_description"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_modify_system_settings"
+        settings:controller="com.android.car.settings.applications.specialaccess.AppOpsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/network_and_internet_fragment.xml b/res/xml/network_and_internet_fragment.xml
new file mode 100644
index 0000000..9539977
--- /dev/null
+++ b/res/xml/network_and_internet_fragment.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/network_and_internet">
+    <com.android.car.settings.common.MasterSwitchPreference
+        android:fragment="com.android.car.settings.wifi.WifiSettingsFragment"
+        android:icon="@drawable/ic_settings_wifi"
+        android:key="@string/pk_wifi_settings_entry"
+        android:title="@string/wifi_settings"
+        settings:controller="com.android.car.settings.wifi.WifiEntryPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.wifi.WifiTetherFragment"
+        android:key="@string/pk_wifi_tether_settings_entry"
+        android:title="@string/tether_settings_title_all"
+        android:icon="@drawable/ic_wifi_tethering"
+        settings:controller="com.android.car.settings.wifi.WifiTetherPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.network.MobileNetworkFragment"
+        android:icon="@drawable/ic_settings_cellular"
+        android:key="@string/pk_mobile_network_settings_entry"
+        android:title="@string/mobile_network_settings"
+        settings:controller="com.android.car.settings.network.MobileNetworkEntryPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.datausage.DataUsageFragment"
+        android:icon="@drawable/ic_settings_data_usage"
+        android:key="@string/pk_data_usage_settings_entry"
+        android:title="@string/data_usage_settings"
+        settings:controller="com.android.car.settings.datausage.DataUsageEntryPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_network_and_internet_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.ia.wireless"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/notification_access_fragment.xml b/res/xml/notification_access_fragment.xml
new file mode 100644
index 0000000..2637bce
--- /dev/null
+++ b/res/xml/notification_access_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/notification_access_title">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_notification_access"
+        settings:controller="com.android.car.settings.applications.specialaccess.NotificationAccessPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/preferred_engine_fragment.xml b/res/xml/preferred_engine_fragment.xml
new file mode 100644
index 0000000..b730caf
--- /dev/null
+++ b/res/xml/preferred_engine_fragment.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/text_to_speech_preferred_engine_settings">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="pk_tts_preferred_engine_options"
+        settings:controller="com.android.car.settings.tts.PreferredEngineOptionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/premium_sms_access_fragment.xml b/res/xml/premium_sms_access_fragment.xml
new file mode 100644
index 0000000..829cfd9
--- /dev/null
+++ b/res/xml/premium_sms_access_fragment.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/premium_sms_access_title">
+    <Preference
+        android:key="@string/pk_premium_sms_access_description"
+        android:selectable="false"
+        android:summary="@string/premium_sms_access_description"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_premium_sms_access"
+        settings:controller="com.android.car.settings.applications.specialaccess.PremiumSmsAccessPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/reset_app_pref_fragment.xml b/res/xml/reset_app_pref_fragment.xml
new file mode 100644
index 0000000..540d0dd
--- /dev/null
+++ b/res/xml/reset_app_pref_fragment.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/reset_app_pref_title">
+    <Preference
+        android:selectable="false"
+        android:title="@string/reset_app_pref_desc"
+        settings:singleLineTitle="false"/>
+</PreferenceScreen>
diff --git a/res/xml/reset_network_confirm_fragment.xml b/res/xml/reset_network_confirm_fragment.xml
new file mode 100644
index 0000000..e861532
--- /dev/null
+++ b/res/xml/reset_network_confirm_fragment.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:title="@string/reset_network_confirm_title">
+    <Preference
+        android:selectable="false"
+        android:title="@string/reset_network_confirm_desc"/>
+</PreferenceScreen>
diff --git a/res/xml/reset_network_fragment.xml b/res/xml/reset_network_fragment.xml
new file mode 100644
index 0000000..789f581
--- /dev/null
+++ b/res/xml/reset_network_fragment.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/reset_network_title">
+    <Preference
+        android:key="@string/pk_reset_network_items"
+        android:selectable="false"
+        android:title="@string/reset_network_desc"
+        settings:controller="com.android.car.settings.system.ResetNetworkItemsPreferenceController"
+        settings:singleLineTitle="false"/>
+    <CheckBoxPreference
+        android:key="@string/pk_reset_esim"
+        android:summary="@string/reset_esim_desc"
+        android:title="@string/reset_esim_title"
+        settings:controller="com.android.car.settings.system.ResetEsimPreferenceController"/>
+    <ListPreference
+        android:key="@string/pk_reset_network_subscription"
+        android:title="@string/reset_network_select"
+        android:widgetLayout="@layout/dropdown_preference_widget"
+        settings:controller="com.android.car.settings.system.ResetNetworkSubscriptionPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/reset_options_fragment.xml b/res/xml/reset_options_fragment.xml
new file mode 100644
index 0000000..3802b46
--- /dev/null
+++ b/res/xml/reset_options_fragment.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/reset_options_title">
+    <Preference
+        android:fragment="com.android.car.settings.system.ResetNetworkFragment"
+        android:key="@string/pk_reset_network"
+        android:title="@string/reset_network_title"
+        settings:controller="com.android.car.settings.system.ResetNetworkEntryPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.system.ResetAppPrefFragment"
+        android:key="@string/pk_reset_app_pref"
+        android:title="@string/reset_app_pref_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.system.MasterClearFragment"
+        android:key="@string/pk_master_clear"
+        android:title="@string/master_clear_title"
+        settings:controller="com.android.car.settings.system.MasterClearEntryPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/sound_settings_fragment.xml b/res/xml/sound_settings_fragment.xml
new file mode 100644
index 0000000..3aab43f
--- /dev/null
+++ b/res/xml/sound_settings_fragment.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/sound_settings">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_volume_settings"
+        settings:controller="com.android.car.settings.sound.VolumeSettingsPreferenceController"/>
+    <com.android.car.settings.sound.RingtonePreference
+        android:key="@string/pk_default_ringtone"
+        android:ringtoneType="ringtone"
+        android:title="@string/ringtone_title"
+        settings:controller="com.android.car.settings.sound.RingtonePreferenceController"/>
+    <com.android.car.settings.sound.RingtonePreference
+        android:key="@string/pk_default_notification"
+        android:ringtoneType="notification"
+        android:title="@string/notification_ringtone_title"
+        settings:controller="com.android.car.settings.sound.RingtonePreferenceController"/>
+    <com.android.car.settings.sound.RingtonePreference
+        android:key="@string/pk_default_alarm"
+        android:ringtoneType="alarm"
+        android:title="@string/alarm_ringtone_title"
+        settings:controller="com.android.car.settings.sound.RingtonePreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_sounds_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.ia.sound"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/special_access_fragment.xml b/res/xml/special_access_fragment.xml
new file mode 100644
index 0000000..ecae227
--- /dev/null
+++ b/res/xml/special_access_fragment.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/special_access">
+    <Preference
+        android:fragment="com.android.car.settings.applications.specialaccess.ModifySystemSettingsFragment"
+        android:key="@string/pk_modify_system_settings_entry"
+        android:title="@string/modify_system_settings_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.applications.specialaccess.NotificationAccessFragment"
+        android:key="@string/pk_notification_access_entry"
+        android:title="@string/notification_access_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.applications.specialaccess.PremiumSmsAccessFragment"
+        android:key="@string/pk_premium_sms_access_entry"
+        android:title="@string/premium_sms_access_title"
+        settings:controller="com.android.car.settings.applications.specialaccess.PremiumSmsAccessEntryPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.applications.specialaccess.UsageAccessFragment"
+        android:key="@string/pk_usage_access_entry"
+        android:title="@string/usage_access_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.applications.specialaccess.DirectoryAccessFragment"
+        android:key="@string/pk_directory_access_entry"
+        android:title="@string/directory_access_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.applications.specialaccess.WifiControlFragment"
+        android:key="@string/pk_wifi_control_entry"
+        android:title="@string/wifi_control_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/storage_media_category_detail_fragment.xml b/res/xml/storage_media_category_detail_fragment.xml
new file mode 100644
index 0000000..14f7207
--- /dev/null
+++ b/res/xml/storage_media_category_detail_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/storage_music_audio">
+
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_storage_music_audio_details"
+        settings:controller="com.android.car.settings.storage.StorageMediaCategoryDetailPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/storage_other_category_detail_fragment.xml b/res/xml/storage_other_category_detail_fragment.xml
new file mode 100644
index 0000000..b01877c
--- /dev/null
+++ b/res/xml/storage_other_category_detail_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/storage_other_apps">
+
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_storage_other_apps_details"
+        settings:controller="com.android.car.settings.storage.StorageApplicationListPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/storage_settings_fragment.xml b/res/xml/storage_settings_fragment.xml
new file mode 100644
index 0000000..1d06a1f
--- /dev/null
+++ b/res/xml/storage_settings_fragment.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/storage_settings_title">
+    <com.android.car.settings.common.ProgressBarPreference
+        android:icon="@drawable/ic_media_stream"
+        android:key="@string/pk_storage_music_audio"
+        android:layout="@layout/progress_bar_preference"
+        android:title="@string/storage_music_audio"
+        settings:controller="com.android.car.settings.storage.StorageMediaCategoryPreferenceController"/>
+    <com.android.car.settings.common.ProgressBarPreference
+        android:fragment="com.android.car.settings.storage.StorageOtherCategoryDetailFragment"
+        android:icon="@drawable/ic_storage_apps"
+        android:key="@string/pk_storage_other_apps"
+        android:layout="@layout/progress_bar_preference"
+        android:title="@string/storage_other_apps"
+        settings:controller="com.android.car.settings.storage.StorageOtherCategoryPreferenceController"/>
+    <com.android.car.settings.common.ProgressBarPreference
+        android:icon="@drawable/ic_folder"
+        android:key="@string/pk_storage_files"
+        android:layout="@layout/progress_bar_preference"
+        android:title="@string/storage_files"
+        settings:controller="com.android.car.settings.storage.StorageFileCategoryPreferenceController"/>
+    <com.android.car.settings.common.ProgressBarPreference
+        android:icon="@drawable/ic_system_update"
+        android:key="@string/pk_storage_system"
+        android:layout="@layout/progress_bar_preference"
+        android:title="@string/storage_system"
+        settings:controller="com.android.car.settings.storage.StorageSystemCategoryPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/system_settings_fragment.xml b/res/xml/system_settings_fragment.xml
new file mode 100644
index 0000000..1ce163a
--- /dev/null
+++ b/res/xml/system_settings_fragment.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/system_setting_title">
+    <Preference
+        android:fragment="com.android.car.settings.language.LanguagesAndInputFragment"
+        android:key="@string/pk_languages_and_input_settings"
+        android:icon="@drawable/ic_language"
+        android:title="@string/languages_and_input_settings"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:icon="@drawable/ic_system_update"
+        android:key="@string/pk_system_update_settings"
+        android:title="@string/system_update_settings_title"
+        settings:controller="com.android.car.settings.system.SystemUpdatePreferenceController">
+        <intent android:action="android.settings.SYSTEM_UPDATE_SETTINGS"/>
+    </Preference>
+    <Preference
+        android:fragment="com.android.car.settings.system.AboutSettingsFragment"
+        android:icon="@drawable/ic_settings_about"
+        android:key="@string/pk_about_settings_entry"
+        android:title="@string/about_settings"
+        settings:controller="com.android.car.settings.system.AboutSettingsEntryPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.system.LegalInformationFragment"
+        android:icon="@drawable/ic_settings_about"
+        android:key="@string/pk_legal_information_entry"
+        android:title="@string/legal_information"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.system.ResetOptionsFragment"
+        android:icon="@drawable/ic_restore"
+        android:key="@string/pk_reset_options_entry"
+        android:summary="@string/reset_options_summary"
+        android:title="@string/reset_options_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:icon="@drawable/ic_settings_development"
+        android:key="@string/pk_developer_options_entry"
+        android:title="@string/developer_options_settings"
+        settings:controller="com.android.car.settings.system.DeveloperOptionsEntryPreferenceController">
+        <intent android:action="android.settings.APPLICATION_DEVELOPMENT_SETTINGS"/>
+    </Preference>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_system_extra_settings"
+        settings:controller="com.android.car.settings.common.ExtraSettingsPreferenceController">
+        <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+            <extra android:name="com.android.settings.category"
+                   android:value="com.android.settings.category.ia.system"/>
+        </intent>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/text_to_speech_output_fragment.xml b/res/xml/text_to_speech_output_fragment.xml
new file mode 100644
index 0000000..fcf8f94
--- /dev/null
+++ b/res/xml/text_to_speech_output_fragment.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/text_to_speech_settings">
+    <com.android.car.settings.common.ButtonPreference
+        android:fragment="com.android.car.settings.tts.PreferredEngineFragment"
+        android:key="@string/pk_tts_preferred_engine_entry"
+        android:title="@string/text_to_speech_preferred_engine_settings"
+        android:widgetLayout="@layout/details_preference_widget"
+        settings:controller="com.android.car.settings.tts.PreferredEngineEntryPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_tts_playback_group"
+        settings:controller="com.android.car.settings.tts.TtsPlaybackPreferenceController">
+        <ListPreference
+            android:key="@string/pk_tts_default_language"
+            android:persistent="false"
+            android:summary="@string/tts_default_lang_summary"
+            android:title="@string/tts_default_lang_title"/>
+        <com.android.car.settings.common.SeekBarPreference
+            android:key="@string/pk_tts_speech_rate"
+            android:title="@string/tts_speech_rate"/>
+        <com.android.car.settings.common.SeekBarPreference
+            android:key="@string/pk_tts_pitch"
+            android:title="@string/tts_pitch"/>
+        <Preference
+            android:key="@string/pk_tts_reset"
+            android:title="@string/tts_reset"/>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+</PreferenceScreen>
diff --git a/res/xml/timezone_picker_screen_fragment.xml b/res/xml/timezone_picker_screen_fragment.xml
new file mode 100644
index 0000000..fab12a8
--- /dev/null
+++ b/res/xml/timezone_picker_screen_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/date_time_set_timezone_title">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_timezone_picker_screen"
+        settings:controller="com.android.car.settings.datetime.TimeZonePickerScreenPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/usage_access_fragment.xml b/res/xml/usage_access_fragment.xml
new file mode 100644
index 0000000..a1ac3e2
--- /dev/null
+++ b/res/xml/usage_access_fragment.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/usage_access_title">
+    <Preference
+        android:key="@string/pk_usage_access_description"
+        android:selectable="false"
+        android:summary="@string/usage_access_description"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_usage_access"
+        settings:controller="com.android.car.settings.applications.specialaccess.AppOpsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/user_details_fragment.xml b/res/xml/user_details_fragment.xml
new file mode 100644
index 0000000..1317453
--- /dev/null
+++ b/res/xml/user_details_fragment.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <com.android.car.settings.common.ButtonPreference
+        android:key="@string/pk_edit_user_name_entry"
+        android:selectable="false"
+        android:widgetLayout="@layout/edit_icon_preference_widget"
+        settings:controller="com.android.car.settings.users.EditUserNameEntryPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/user_details_permissions_fragment.xml b/res/xml/user_details_permissions_fragment.xml
new file mode 100644
index 0000000..a71a604
--- /dev/null
+++ b/res/xml/user_details_permissions_fragment.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <com.android.car.settings.common.ButtonPreference
+        android:key="@string/pk_make_user_admin"
+        android:selectable="false"
+        android:title="@string/grant_admin_permissions_title"
+        android:widgetLayout="@layout/make_admin_preference_widget"
+        settings:controller="com.android.car.settings.users.MakeAdminPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_user_permissions"
+        settings:controller="com.android.car.settings.users.PermissionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/users_list_fragment.xml b/res/xml/users_list_fragment.xml
new file mode 100644
index 0000000..d483ffe
--- /dev/null
+++ b/res/xml/users_list_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/users_list_title">
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_users_list"
+        settings:controller="com.android.car.settings.users.UsersListPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/wifi_control_fragment.xml b/res/xml/wifi_control_fragment.xml
new file mode 100644
index 0000000..58d4b0e
--- /dev/null
+++ b/res/xml/wifi_control_fragment.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/wifi_control_title">
+    <Preference
+        android:key="@string/pk_wifi_control_description"
+        android:selectable="false"
+        android:summary="@string/wifi_control_description"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_wifi_control"
+        settings:controller="com.android.car.settings.applications.specialaccess.WifiControlPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/wifi_detail_fragment.xml b/res/xml/wifi_detail_fragment.xml
new file mode 100644
index 0000000..a681f4b
--- /dev/null
+++ b/res/xml/wifi_detail_fragment.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/wifi_settings">
+    <!-- General Details Category -->
+    <com.android.car.settings.common.LogicalPreferenceGroup>
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_signal_strength"
+            android:selectable="false"
+            android:title="@string/wifi_signal_strength"
+            settings:controller="com.android.car.settings.wifi.details.WifiSignalStrengthPreferenceController"/>
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_frequency"
+            android:selectable="false"
+            android:title="@string/wifi_frequency"
+            settings:controller="com.android.car.settings.wifi.details.WifiFrequencyPreferenceController"/>
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_security"
+            android:selectable="false"
+            android:title="@string/wifi_security"
+            settings:controller="com.android.car.settings.wifi.details.WifiSecurityPreferenceController"/>
+    </com.android.car.settings.common.LogicalPreferenceGroup>
+
+    <!-- Ip Details Category -->
+    <PreferenceCategory
+        android:title="@string/wifi_network_detail">
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_mac_address"
+            android:selectable="false"
+            android:title="@string/wifi_mac_address"
+            settings:controller="com.android.car.settings.wifi.details.WifiMacAddressPreferenceController"/>
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_ip"
+            android:selectable="false"
+            android:title="@string/wifi_ip_address"
+            settings:controller="com.android.car.settings.wifi.details.WifiIpAddressPreferenceController"/>
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_gateway"
+            android:selectable="false"
+            android:title="@string/wifi_gateway"
+            settings:controller="com.android.car.settings.wifi.details.WifiGatewayPreferenceController"/>
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_subnet_mask"
+            android:selectable="false"
+            android:title="@string/wifi_subnet_mask"
+            settings:controller="com.android.car.settings.wifi.details.WifiSubnetPreferenceController"/>
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_dns"
+            android:selectable="false"
+            android:title="@string/wifi_dns"
+            settings:controller="com.android.car.settings.wifi.details.WifiDnsPreferenceController"/>
+        <com.android.car.settings.wifi.details.WifiDetailsPreference
+            android:key="@string/pk_wifi_link_speed"
+            android:selectable="false"
+            android:title="@string/wifi_speed"
+            settings:controller="com.android.car.settings.wifi.details.WifiLinkSpeedPreferenceController"/>
+    </PreferenceCategory>
+
+    <!-- IPv6 Details -->
+    <PreferenceCategory
+        android:selectable="false"
+        android:title="@string/wifi_details_ipv6_address_header">
+        <Preference
+            android:key="@string/pk_wifi_ipv6"
+            android:selectable="false"
+            settings:controller="com.android.car.settings.wifi.details.WifiIpv6AddressPreferenceController"/>
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/res/xml/wifi_list_fragment.xml b/res/xml/wifi_list_fragment.xml
new file mode 100644
index 0000000..bcd287f
--- /dev/null
+++ b/res/xml/wifi_list_fragment.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/wifi_settings">
+    <Preference
+        android:key="@string/pk_wifi_status"
+        settings:controller="com.android.car.settings.wifi.WifiStatusPreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_wifi_list"
+        settings:controller="com.android.car.settings.wifi.AccessPointListPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.wifi.AddWifiFragment"
+        android:icon="@drawable/ic_add"
+        android:key="@string/pk_add_wifi"
+        android:title="@string/wifi_setup_add_network"
+        settings:controller="com.android.car.settings.wifi.AddWifiPreferenceController"/>
+    <Preference
+        android:fragment="com.android.car.settings.wifi.preferences.WifiPreferencesFragment"
+        android:key="@string/pk_wifi_preferences"
+        android:title="@string/wifi_preferences_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/wifi_preferences_fragment.xml b/res/xml/wifi_preferences_fragment.xml
new file mode 100644
index 0000000..f19d174
--- /dev/null
+++ b/res/xml/wifi_preferences_fragment.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/wifi_preferences_title">
+    <SwitchPreference
+        android:key="@string/pk_enable_wifi_wakeup"
+        android:title="@string/wifi_wakeup"
+        android:icon="@drawable/ic_settings_auto_wifi"
+        android:summary="@string/wifi_wakeup_summary"
+        settings:controller="com.android.car.settings.wifi.preferences.WifiWakeupTogglePreferenceController"/>
+    <SwitchPreference
+        android:key="@string/pk_wifi_cellular_fallback"
+        android:summary="@string/wifi_cellular_fallback_summary"
+        android:title="@string/wifi_cellular_fallback_title"
+        settings:controller="com.android.car.settings.wifi.preferences.CellularFallbackTogglePreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/wifi_tether_fragment.xml b/res/xml/wifi_tether_fragment.xml
new file mode 100644
index 0000000..a0d1bcf
--- /dev/null
+++ b/res/xml/wifi_tether_fragment.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/tether_settings_title_all" >
+    <com.android.car.settings.common.ValidatedEditTextPreference
+        android:key="@string/pk_wifi_tether_name"
+        android:title="@string/wifi_hotspot_name_title"
+        settings:controller="com.android.car.settings.wifi.WifiTetherNamePreferenceController"/>
+    <ListPreference
+        android:key="@string/pk_wifi_tether_security"
+        android:title="@string/wifi_hotspot_security_title"
+        android:dialogTitle="@string/wifi_hotspot_security_title"
+        android:persistent="false"
+        settings:controller="com.android.car.settings.wifi.WifiTetherSecurityPreferenceController"/>
+    <com.android.car.settings.common.ValidatedEditTextPreference
+        android:key="@string/pk_wifi_tether_password"
+        android:title="@string/wifi_hotspot_password_title"
+        android:persistent="false"
+        settings:controller="com.android.car.settings.wifi.WifiTetherPasswordPreferenceController"/>
+    <SwitchPreference
+        android:key="@string/pk_wifi_tether_auto_off"
+        android:title="@string/wifi_hotspot_auto_off_title"
+        android:summary="@string/wifi_hotspot_auto_off_summary"
+        android:persistent="false"
+        settings:controller="com.android.car.settings.wifi.WifiTetherAutoOffPreferenceController"/>
+    <ListPreference
+        android:key="@string/pk_wifi_tether_ap_band"
+        android:title="@string/wifi_hotspot_ap_band_title"
+        android:dialogTitle="@string/wifi_hotspot_ap_band_title"
+        android:persistent="false"
+        settings:controller="com.android.car.settings.wifi.WifiTetherApBandPreferenceController"/>
+</PreferenceScreen>
diff --git a/src/com/android/car/settings/FallbackHome.java b/src/com/android/car/settings/FallbackHome.java
new file mode 100644
index 0000000..41ef281
--- /dev/null
+++ b/src/com/android/car/settings/FallbackHome.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2018 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.car.settings;
+
+import android.app.Activity;
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.app.WallpaperManager.OnColorsChangedListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.AnimationUtils;
+
+import com.android.car.settings.common.Logger;
+
+import java.util.Objects;
+
+/**
+ * Copied over from phone settings. This covers the fallback case where no launcher is available.
+ */
+public class FallbackHome extends Activity {
+    private static final Logger LOG = new Logger(FallbackHome.class);
+    private static final int PROGRESS_TIMEOUT = 2000;
+
+    private boolean mProvisioned;
+    private WallpaperManager mWallManager;
+
+    private final Runnable mProgressTimeoutRunnable = () -> {
+        View v = getLayoutInflater().inflate(
+                R.layout.fallback_home_finishing_boot, null /* root */);
+        setContentView(v);
+        v.setAlpha(0f);
+        v.animate()
+                .alpha(1f)
+                .setDuration(500)
+                .setInterpolator(AnimationUtils.loadInterpolator(
+                        this, android.R.interpolator.fast_out_slow_in))
+                .start();
+        getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
+    };
+
+    private final OnColorsChangedListener mColorsChangedListener = new OnColorsChangedListener() {
+        @Override
+        public void onColorsChanged(WallpaperColors colors, int which) {
+            if (colors != null) {
+                View decorView = getWindow().getDecorView();
+                decorView.setSystemUiVisibility(
+                        updateVisibilityFlagsFromColors(colors, decorView.getSystemUiVisibility()));
+                mWallManager.removeOnColorsChangedListener(this);
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Set ourselves totally black before the device is provisioned so that
+        // we don't flash the wallpaper before SUW
+        mProvisioned = Settings.Global.getInt(getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        int flags;
+        if (!mProvisioned) {
+            setTheme(R.style.FallbackHome_SetupWizard);
+            flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+        } else {
+            flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        }
+
+        // Set the system ui flags to light status bar if the wallpaper supports dark text to match
+        // current system ui color tints. Use a listener to wait for colors if not ready yet.
+        mWallManager = getSystemService(WallpaperManager.class);
+        if (mWallManager == null) {
+            LOG.w("Wallpaper manager isn't ready, can't listen to color changes!");
+        } else {
+            WallpaperColors colors = mWallManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+            if (colors == null) {
+                mWallManager.addOnColorsChangedListener(mColorsChangedListener, null /* handler */);
+            } else {
+                flags = updateVisibilityFlagsFromColors(colors, flags);
+            }
+        }
+        getWindow().getDecorView().setSystemUiVisibility(flags);
+
+        registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+        maybeFinish();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mProvisioned) {
+            mHandler.postDelayed(mProgressTimeoutRunnable, PROGRESS_TIMEOUT);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mHandler.removeCallbacks(mProgressTimeoutRunnable);
+    }
+
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+        if (mWallManager != null) {
+            mWallManager.removeOnColorsChangedListener(mColorsChangedListener);
+        }
+    }
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            maybeFinish();
+        }
+    };
+
+    private void maybeFinish() {
+        if (getSystemService(UserManager.class).isUserUnlocked()) {
+            final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_HOME);
+            final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
+            if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
+                if (UserManager.isSplitSystemUser()
+                        && UserHandle.myUserId() == UserHandle.USER_SYSTEM) {
+                    // This avoids the situation where the system user has no home activity after
+                    // SUW and this activity continues to throw out warnings. See b/28870689.
+                    return;
+                }
+                LOG.d("User unlocked but no home; let's hope someone enables one soon?");
+                mHandler.sendEmptyMessageDelayed(0, 500);
+            } else {
+                LOG.d("User unlocked and real home found; let's go!");
+                getSystemService(PowerManager.class).userActivity(
+                        SystemClock.uptimeMillis(), false);
+                finish();
+            }
+        }
+    }
+
+    private int updateVisibilityFlagsFromColors(WallpaperColors colors, int flags) {
+        if ((colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0) {
+            return flags | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+                    | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+        }
+        return flags & ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
+                & ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+    }
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            maybeFinish();
+        }
+    };
+}
diff --git a/src/com/android/car/settings/accounts/AccountAutoSyncPreferenceController.java b/src/com/android/car/settings/accounts/AccountAutoSyncPreferenceController.java
new file mode 100644
index 0000000..80d6a83
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountAutoSyncPreferenceController.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller for the preference that allows the user to toggle automatic syncing of accounts.
+ *
+ * <p>Copied from {@link com.android.settings.users.AutoSyncDataPreferenceController}
+ */
+public class AccountAutoSyncPreferenceController extends PreferenceController<TwoStatePreference> {
+
+    private final UserHandle mUserHandle;
+    /**
+     * Argument key to store a value that indicates whether the account auto sync is being enabled
+     * or disabled.
+     */
+    @VisibleForTesting
+    static final String KEY_ENABLING = "ENABLING";
+    /** Argument key to store user handle. */
+    @VisibleForTesting
+    static final String KEY_USER_HANDLE = "USER_HANDLE";
+
+    @VisibleForTesting
+    final ConfirmationDialogFragment.ConfirmListener mConfirmListener = arguments -> {
+        boolean enabling = arguments.getBoolean(KEY_ENABLING);
+        UserHandle userHandle = arguments.getParcelable(KEY_USER_HANDLE);
+        ContentResolver.setMasterSyncAutomaticallyAsUser(enabling, userHandle.getIdentifier());
+        getPreference().setChecked(enabling);
+    };
+
+    public AccountAutoSyncPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        CarUserManagerHelper carUserManagerHelper = new CarUserManagerHelper(context);
+        mUserHandle = carUserManagerHelper.getCurrentProcessUserInfo().getUserHandle();
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
+                mUserHandle.getIdentifier()));
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        // If the dialog is still up, reattach the preference
+        ConfirmationDialogFragment dialog =
+                (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
+                        ConfirmationDialogFragment.TAG);
+
+        ConfirmationDialogFragment.resetListeners(dialog, mConfirmListener, /* rejectListener= */
+                null);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object checked) {
+        getFragmentController().showDialog(
+                getAutoSyncChangeConfirmationDialogFragment((boolean) checked),
+                ConfirmationDialogFragment.TAG);
+        // The dialog will change the state of the preference if the user confirms, so don't handle
+        // it here
+        return false;
+    }
+
+    private ConfirmationDialogFragment getAutoSyncChangeConfirmationDialogFragment(
+            boolean enabling) {
+        int dialogTitle;
+        int dialogMessage;
+
+        if (enabling) {
+            dialogTitle = R.string.data_usage_auto_sync_on_dialog_title;
+            dialogMessage = R.string.data_usage_auto_sync_on_dialog;
+        } else {
+            dialogTitle = R.string.data_usage_auto_sync_off_dialog_title;
+            dialogMessage = R.string.data_usage_auto_sync_off_dialog;
+        }
+
+        ConfirmationDialogFragment dialogFragment =
+                new ConfirmationDialogFragment.Builder(getContext())
+                        .setTitle(dialogTitle).setMessage(dialogMessage)
+                        .setPositiveButton(android.R.string.ok, mConfirmListener)
+                        .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null)
+                        .addArgumentBoolean(KEY_ENABLING, enabling)
+                        .addArgumentParcelable(KEY_USER_HANDLE, mUserHandle)
+                        .build();
+
+        return dialogFragment;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/car/settings/accounts/AccountDetailsFragment.java b/src/com/android/car/settings/accounts/AccountDetailsFragment.java
index 1f28adf..465cde8 100644
--- a/src/com/android/car/settings/accounts/AccountDetailsFragment.java
+++ b/src/com/android/car/settings/accounts/AccountDetailsFragment.java
@@ -18,144 +18,167 @@
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
 import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
 import android.app.Activity;
+import android.app.AlertDialog;
 import android.app.Dialog;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.UserInfo;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.Fragment;
-import android.text.TextUtils;
+import android.view.View;
 import android.widget.Button;
+import android.widget.TextView;
 
-import androidx.car.app.CarAlertDialog;
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
+import com.android.car.settings.common.ErrorDialog;
 import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
 import com.android.settingslib.accounts.AuthenticatorHelper;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
 
 /**
  * Shows account details, and delete account option.
  */
-public class AccountDetailsFragment extends ListItemSettingsFragment
-        implements AuthenticatorHelper.OnAccountsUpdateListener {
-    public static final String EXTRA_ACCOUNT_INFO = "extra_account_info";
+public class AccountDetailsFragment extends SettingsFragment implements
+        AuthenticatorHelper.OnAccountsUpdateListener {
+    public static final String EXTRA_ACCOUNT = "extra_account";
+    public static final String EXTRA_ACCOUNT_LABEL = "extra_account_label";
     public static final String EXTRA_USER_INFO = "extra_user_info";
 
     private Account mAccount;
     private UserInfo mUserInfo;
-    private ListItemProvider mItemProvider;
-    private AccountManagerHelper mAccountManagerHelper;
+    private AuthenticatorHelper mAuthenticatorHelper;
 
-    public static AccountDetailsFragment newInstance(
-            Account account, UserInfo userInfo) {
+    /**
+     * Creates a new AccountDetailsFragment.
+     *
+     * <p>Passes the provided account, label, and user info to the fragment via fragment arguments.
+     */
+    public static AccountDetailsFragment newInstance(Account account, CharSequence label,
+            UserInfo userInfo) {
         AccountDetailsFragment
                 accountDetailsFragment = new AccountDetailsFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_TITLE_ID, R.string.account_details_title);
-        bundle.putParcelable(EXTRA_ACCOUNT_INFO, account);
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EXTRA_ACCOUNT, account);
+        bundle.putCharSequence(EXTRA_ACCOUNT_LABEL, label);
         bundle.putParcelable(EXTRA_USER_INFO, userInfo);
         accountDetailsFragment.setArguments(bundle);
         return accountDetailsFragment;
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mAccount = getArguments().getParcelable(EXTRA_ACCOUNT_INFO);
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.account_details_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        mAccount = getArguments().getParcelable(EXTRA_ACCOUNT);
         mUserInfo = getArguments().getParcelable(EXTRA_USER_INFO);
+
+        use(AccountDetailsPreferenceController.class, R.string.pk_account_details)
+                .setAccount(mAccount);
+        use(AccountDetailsPreferenceController.class, R.string.pk_account_details)
+                .setUserHandle(mUserInfo.getUserHandle());
+
+        use(AccountSyncPreferenceController.class, R.string.pk_account_sync)
+                .setAccount(mAccount);
+        use(AccountDetailsSettingController.class, R.string.pk_account_settings)
+                .setAccount(mAccount);
+        use(AccountSyncPreferenceController.class, R.string.pk_account_sync)
+                .setUserHandle(mUserInfo.getUserHandle());
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
-        // Should be created before calling getListItem().
-        mAccountManagerHelper = new AccountManagerHelper(getContext(), this);
-        mAccountManagerHelper.startListeningToAccountUpdates();
-
-        mItemProvider = new ListItemProvider.ListProvider(getListItems());
-
-        // Super is called only AFTER item provider is instantiated, because
-        // super.onActivityCreated calls getItemProvider().
         super.onActivityCreated(savedInstanceState);
 
-        // Title was set in super.onActivityCreated, but override if account label is available.
-        setFragmentTitle();
+        // Set the fragment's title
+        TextView titleView = requireActivity().findViewById(R.id.title);
+        titleView.setText(getArguments().getCharSequence(EXTRA_ACCOUNT_LABEL));
 
-        showRemoveButton();
+        // Enable the remove account button if the user is allowed to modify accounts.
+        Button removeAccountButton = requireActivity().findViewById(R.id.action_button1);
+        if (new CarUserManagerHelper(getContext()).canCurrentProcessModifyAccounts()) {
+            removeAccountButton.setText(R.string.remove_button);
+            removeAccountButton.setOnClickListener(v -> onRemoveAccountClicked());
+        } else {
+            removeAccountButton.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        mAuthenticatorHelper = new AuthenticatorHelper(getContext(), mUserInfo.getUserHandle(),
+                this);
+        mAuthenticatorHelper.listenToAccountUpdates();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mAuthenticatorHelper.stopListeningToAccountUpdates();
     }
 
     @Override
     public void onAccountsUpdate(UserHandle userHandle) {
-        if (!mAccountManagerHelper.accountExists(mAccount)) {
+        if (!accountExists()) {
             // The account was deleted. Pop back.
-            getFragmentController().goBack();
+            goBack();
         }
     }
 
-    @Override
-    public ListItemProvider getItemProvider() {
-        return mItemProvider;
-    }
-
-    private void showRemoveButton() {
-        Button removeAccountBtn = getActivity().findViewById(R.id.action_button1);
-        removeAccountBtn.setText(R.string.remove_button);
-        removeAccountBtn.setOnClickListener(v -> removeAccount());
-    }
-
-    private void setFragmentTitle() {
-        CharSequence accountLabel = mAccountManagerHelper.getLabelForType(mAccount.type);
-        if (!TextUtils.isEmpty(accountLabel)) {
-            setTitle(accountLabel);
+    /** Returns whether the account being shown by this fragment still exists. */
+    @VisibleForTesting
+    boolean accountExists() {
+        if (mAccount == null) {
+            return false;
         }
+
+        Account[] accounts = AccountManager.get(getContext()).getAccountsByTypeAsUser(mAccount.type,
+                mUserInfo.getUserHandle());
+
+        return Arrays.asList(accounts).contains(mAccount);
     }
 
-    private List<ListItem> getListItems() {
-        Drawable icon = mAccountManagerHelper.getDrawableForType(mAccount.type);
-
-        TextListItem item = new TextListItem(getContext());
-        item.setPrimaryActionIcon(icon, /* useLargeIcon= */ false);
-        item.setTitle(mAccount.name);
-
-        List<ListItem> items = new ArrayList<>();
-        items.add(item);
-        return items;
-    }
-
-    public void removeAccount() {
-        ConfirmRemoveAccountDialog.show(this, mAccount, mUserInfo.getUserHandle());
+    private void onRemoveAccountClicked() {
+        ConfirmRemoveAccountDialogFragment.show(this, mAccount, mUserInfo.getUserHandle());
     }
 
     /**
      * Dialog to confirm with user about account removal
      */
-    public static class ConfirmRemoveAccountDialog extends DialogFragment implements
+    public static class ConfirmRemoveAccountDialogFragment extends DialogFragment implements
             DialogInterface.OnClickListener {
         private static final String KEY_ACCOUNT = "account";
         private static final String DIALOG_TAG = "confirmRemoveAccount";
-        private static final Logger LOG = new Logger(ConfirmRemoveAccountDialog.class);
-        private Account mAccount;
-        private UserHandle mUserHandle;
-
+        private static final Logger LOG = new Logger(ConfirmRemoveAccountDialogFragment.class);
         private final AccountManagerCallback<Bundle> mCallback =
-            new AccountManagerCallback<Bundle>() {
-                @Override
-                public void run(AccountManagerFuture<Bundle> future) {
+                future -> {
                     // If already out of this screen, don't proceed.
                     if (!getTargetFragment().isResumed()) {
                         return;
@@ -164,22 +187,26 @@
                     boolean success = false;
                     try {
                         success =
-                                future.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+                                future.getResult().getBoolean(
+                                        AccountManager.KEY_BOOLEAN_RESULT);
                     } catch (OperationCanceledException | IOException | AuthenticatorException e) {
                         LOG.v("removeAccount error: " + e);
                     }
                     final Activity activity = getTargetFragment().getActivity();
                     if (!success && activity != null && !activity.isFinishing()) {
-                        RemoveAccountFailureDialog.show(getTargetFragment());
+                        ErrorDialog.show(getTargetFragment(),
+                                R.string.remove_account_error_title);
                     } else {
                         getTargetFragment().getFragmentManager().popBackStack();
                     }
-                }
-            };
+                };
+        private Account mAccount;
+        private UserHandle mUserHandle;
 
         public static void show(
                 Fragment parent, Account account, UserHandle userHandle) {
-            final ConfirmRemoveAccountDialog dialog = new ConfirmRemoveAccountDialog();
+            final ConfirmRemoveAccountDialogFragment dialog =
+                    new ConfirmRemoveAccountDialogFragment();
             Bundle bundle = new Bundle();
             bundle.putParcelable(KEY_ACCOUNT, account);
             bundle.putParcelable(Intent.EXTRA_USER, userHandle);
@@ -198,9 +225,9 @@
 
         @Override
         public Dialog onCreateDialog(Bundle savedInstanceState) {
-            return new CarAlertDialog.Builder(getContext())
+            return new AlertDialog.Builder(getContext())
                     .setTitle(R.string.really_remove_account_title)
-                    .setBody(R.string.really_remove_account_message)
+                    .setMessage(R.string.really_remove_account_message)
                     .setNegativeButton(android.R.string.cancel, null)
                     .setPositiveButton(R.string.remove_account_title, this)
                     .create();
@@ -214,28 +241,4 @@
             dialog.dismiss();
         }
     }
-
-    /**
-     * Dialog to tell user about account removal failure
-     */
-    public static class RemoveAccountFailureDialog extends DialogFragment {
-
-        private static final String DIALOG_TAG = "removeAccountFailed";
-
-        public static void show(Fragment parent) {
-            final RemoveAccountFailureDialog dialog = new RemoveAccountFailureDialog();
-            dialog.setTargetFragment(parent, 0);
-            dialog.show(parent.getFragmentManager(), DIALOG_TAG);
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            return new CarAlertDialog.Builder(getContext())
-                    .setTitle(R.string.really_remove_account_title)
-                    .setBody(R.string.remove_account_failed)
-                    .setPositiveButton(android.R.string.ok, null)
-                    .create();
-        }
-
-    }
 }
diff --git a/src/com/android/car/settings/accounts/AccountDetailsPreferenceController.java b/src/com/android/car/settings/accounts/AccountDetailsPreferenceController.java
new file mode 100644
index 0000000..c47efba
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountDetailsPreferenceController.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.accounts.Account;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.annotation.CallSuper;
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.accounts.AuthenticatorHelper;
+
+/** Controller for the preference that shows the details of an account. */
+public class AccountDetailsPreferenceController extends PreferenceController<Preference> {
+    private static final Logger LOG = new Logger(AccountDetailsPreferenceController.class);
+
+    private Account mAccount;
+    private UserHandle mUserHandle;
+
+    public AccountDetailsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /** Sets the account that the details are shown for. */
+    public void setAccount(Account account) {
+        mAccount = account;
+    }
+
+    /** Returns the account the details are shown for. */
+    public Account getAccount() {
+        return mAccount;
+    }
+
+    /** Sets the UserHandle used by the controller. */
+    public void setUserHandle(UserHandle userHandle) {
+        mUserHandle = userHandle;
+    }
+
+    /** Returns the UserHandle used by the controller. */
+    public UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /**
+     * Verifies that the controller was properly initialized with
+     * {@link #setAccount(Account)} and {@link #setUserHandle(UserHandle)}.
+     *
+     * @throws IllegalStateException if the account or user handle are {@code null}
+     */
+    @Override
+    @CallSuper
+    protected void checkInitialized() {
+        LOG.v("checkInitialized");
+        if (mAccount == null) {
+            throw new IllegalStateException(
+                    "AccountDetailsPreferenceController must be initialized by calling "
+                            + "setAccount(Account)");
+        }
+        if (mUserHandle == null) {
+            throw new IllegalStateException(
+                    "AccountDetailsPreferenceController must be initialized by calling "
+                            + "setUserHandle(UserHandle)");
+        }
+    }
+
+    @Override
+    @CallSuper
+    protected void updateState(Preference preference) {
+        preference.setTitle(mAccount.name);
+        // Get the icon corresponding to the account's type and set it.
+        AuthenticatorHelper helper = new AuthenticatorHelper(getContext(), mUserHandle, null);
+        preference.setIcon(helper.getDrawableForType(getContext(), mAccount.type));
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountDetailsSettingController.java b/src/com/android/car/settings/accounts/AccountDetailsSettingController.java
new file mode 100644
index 0000000..056e643
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountDetailsSettingController.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.accounts;
+
+import android.accounts.Account;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.CallSuper;
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.ExtraSettingsPreferenceController;
+import com.android.car.settings.common.FragmentController;
+
+import java.util.Map;
+
+/**
+ * Injects preferences from other system applications at a placeholder location into the account
+ * details fragment. This class is and extension of {@link ExtraSettingsPreferenceController} which
+ * is needed to check what all preferences to show in the account details page.
+ */
+public class AccountDetailsSettingController extends ExtraSettingsPreferenceController {
+
+    private static final String METADATA_IA_ACCOUNT = "com.android.settings.ia.account";
+    private Account mAccount;
+
+    public AccountDetailsSettingController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions restrictionInfo) {
+        super(context, preferenceKey, fragmentController, restrictionInfo);
+    }
+
+    /** Sets the account that the preferences are being shown for. */
+    public void setAccount(Account account) {
+        mAccount = account;
+    }
+
+    @Override
+    @CallSuper
+    protected void checkInitialized() {
+        if (mAccount == null) {
+            throw new IllegalStateException(
+                    "AccountDetailsSettingController must be initialized by calling "
+                            + "setAccount(Account)");
+        }
+    }
+
+    @Override
+    @CallSuper
+    protected void addExtraSettings(Map<Preference, Bundle> preferenceBundleMap) {
+        for (Preference setting : preferenceBundleMap.keySet()) {
+            if (mAccount != null && !mAccount.type.equals(
+                    preferenceBundleMap.get(setting).getString(METADATA_IA_ACCOUNT))) {
+                continue;
+            }
+            getPreference().addPreference(setting);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountDetailsWithSyncStatusPreferenceController.java b/src/com/android/car/settings/accounts/AccountDetailsWithSyncStatusPreferenceController.java
new file mode 100644
index 0000000..0def505
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountDetailsWithSyncStatusPreferenceController.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.accounts;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncAdapterType;
+import android.content.SyncInfo;
+import android.content.SyncStatusInfo;
+import android.content.SyncStatusObserver;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Controller for the preference that shows information about an account, including info about
+ * failures.
+ */
+public class AccountDetailsWithSyncStatusPreferenceController extends
+        AccountDetailsPreferenceController {
+    private boolean mIsStarted = false;
+    private Object mStatusChangeListenerHandle;
+    private SyncStatusObserver mSyncStatusObserver =
+            which -> ThreadUtils.postOnMainThread(() -> {
+                // The observer call may occur even if the fragment hasn't been started, so
+                // only force an update if the fragment hasn't been stopped.
+                if (mIsStarted) {
+                    refreshUi();
+                }
+            });
+
+    public AccountDetailsWithSyncStatusPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+
+    /**
+     * Registers the account update and sync status change callbacks.
+     */
+    @Override
+    protected void onStartInternal() {
+        mIsStarted = true;
+        mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener(
+                ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
+                        | ContentResolver.SYNC_OBSERVER_TYPE_STATUS
+                        | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, mSyncStatusObserver);
+    }
+
+    /**
+     * Unregisters the account update and sync status change callbacks.
+     */
+    @Override
+    protected void onStopInternal() {
+        mIsStarted = false;
+        if (mStatusChangeListenerHandle != null) {
+            ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
+        }
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        super.updateState(preference);
+        if (isSyncFailing()) {
+            preference.setSummary(R.string.sync_is_failing);
+        } else {
+            preference.setSummary("");
+        }
+    }
+
+    private boolean isSyncFailing() {
+        int userId = getUserHandle().getIdentifier();
+        List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
+        boolean syncIsFailing = false;
+
+        Set<SyncAdapterType> syncAdapters = AccountSyncHelper.getVisibleSyncAdaptersForAccount(
+                getContext(), getAccount(), getUserHandle());
+        for (SyncAdapterType syncAdapter : syncAdapters) {
+            String authority = syncAdapter.authority;
+
+            SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(getAccount(), authority,
+                    userId);
+            boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(getAccount(),
+                    authority, userId);
+            boolean activelySyncing = AccountSyncHelper.isSyncing(getAccount(), currentSyncs,
+                    authority);
+
+            AccountSyncHelper.SyncState syncState = AccountSyncHelper.getSyncState(status,
+                    syncEnabled, activelySyncing);
+
+            boolean syncIsPending = status != null && status.pending;
+            if (syncState == AccountSyncHelper.SyncState.FAILED && !activelySyncing
+                    && !syncIsPending) {
+                syncIsFailing = true;
+            }
+        }
+
+        return syncIsFailing;
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountListPreferenceController.java b/src/com/android/car/settings/accounts/AccountListPreferenceController.java
new file mode 100644
index 0000000..0f98700
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountListPreferenceController.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import androidx.collection.ArrayMap;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.accounts.AuthenticatorHelper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Controller for listing accounts.
+ *
+ * <p>Largely derived from {@link com.android.settings.accounts.AccountPreferenceController}
+ */
+public class AccountListPreferenceController extends
+        PreferenceController<PreferenceCategory> implements
+        AuthenticatorHelper.OnAccountsUpdateListener,
+        CarUserManagerHelper.OnUsersUpdateListener {
+    private static final String NO_ACCOUNT_PREF_KEY = "no_accounts_added";
+
+    private final UserInfo mUserInfo;
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final ArrayMap<String, Preference> mPreferences = new ArrayMap<>();
+    private AuthenticatorHelper mAuthenticatorHelper;
+    private String[] mAuthorities;
+
+    public AccountListPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        mUserInfo = mCarUserManagerHelper.getCurrentProcessUserInfo();
+        mAuthenticatorHelper = new AuthenticatorHelper(context,
+                mUserInfo.getUserHandle(), /* listener= */ this);
+    }
+
+    /** Sets the account authorities that are available. */
+    public void setAuthorities(String[] authorities) {
+        mAuthorities = authorities;
+    }
+
+    @Override
+    protected Class<PreferenceCategory> getPreferenceType() {
+        return PreferenceCategory.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceCategory preference) {
+        forceUpdateAccountsCategory();
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return mCarUserManagerHelper.canCurrentProcessModifyAccounts() ? AVAILABLE
+                : DISABLED_FOR_USER;
+    }
+
+    /**
+     * Registers the account update and user update callbacks.
+     */
+    @Override
+    protected void onStartInternal() {
+        mAuthenticatorHelper.listenToAccountUpdates();
+        mCarUserManagerHelper.registerOnUsersUpdateListener(this);
+    }
+
+    /**
+     * Unregisters the account update and user update callbacks.
+     */
+    @Override
+    protected void onStopInternal() {
+        mAuthenticatorHelper.stopListeningToAccountUpdates();
+        mCarUserManagerHelper.unregisterOnUsersUpdateListener(this);
+    }
+
+    @Override
+    public void onAccountsUpdate(UserHandle userHandle) {
+        if (userHandle.equals(mUserInfo.getUserHandle())) {
+            forceUpdateAccountsCategory();
+        }
+    }
+
+    @Override
+    public void onUsersUpdate() {
+        forceUpdateAccountsCategory();
+    }
+
+    private boolean onAccountPreferenceClicked(AccountPreference preference) {
+        // Show the account's details when an account is clicked on.
+        getFragmentController().launchFragment(AccountDetailsFragment.newInstance(
+                preference.getAccount(), preference.getLabel(), mUserInfo));
+        return true;
+    }
+
+    /** Forces a refresh of the account preferences. */
+    private void forceUpdateAccountsCategory() {
+        // Set the category title and include the user's name
+        getPreference().setTitle(
+                getContext().getString(R.string.account_list_title, mUserInfo.name));
+
+        // Recreate the authentication helper to refresh the list of enabled accounts
+        mAuthenticatorHelper = new AuthenticatorHelper(getContext(), mUserInfo.getUserHandle(),
+                this);
+
+        Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
+        List<? extends Preference> preferences = getAccountPreferences(preferencesToRemove);
+        // Add all preferences that aren't already shown. Manually set the order so that existing
+        // preferences are reordered correctly.
+        for (int i = 0; i < preferences.size(); i++) {
+            Preference pref = preferences.get(i);
+            pref.setOrder(i);
+            mPreferences.put(pref.getKey(), pref);
+            getPreference().addPreference(pref);
+        }
+
+        for (String key : preferencesToRemove) {
+            getPreference().removePreference(mPreferences.get(key));
+            mPreferences.remove(key);
+        }
+    }
+
+    /**
+     * Returns a list of preferences corresponding to the accounts for the current user.
+     *
+     * <p> Derived from
+     * {@link com.android.settings.accounts.AccountPreferenceController#getAccountTypePreferences}
+     *
+     * @param preferencesToRemove the current preferences shown; only preferences to be removed will
+     *                            remain after method execution
+     */
+    private List<? extends Preference> getAccountPreferences(
+            Set<String> preferencesToRemove) {
+        String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
+        ArrayList<AccountPreference> accountPreferences =
+                new ArrayList<>(accountTypes.length);
+
+        for (int i = 0; i < accountTypes.length; i++) {
+            String accountType = accountTypes[i];
+            // Skip showing any account that does not have any of the requested authorities
+            if (!accountTypeHasAnyRequestedAuthorities(accountType)) {
+                continue;
+            }
+            CharSequence label = mAuthenticatorHelper.getLabelForType(getContext(), accountType);
+            if (label == null) {
+                continue;
+            }
+
+            Account[] accounts = AccountManager.get(getContext())
+                    .getAccountsByTypeAsUser(accountType, mUserInfo.getUserHandle());
+            Drawable icon = mAuthenticatorHelper.getDrawableForType(getContext(), accountType);
+
+            // Add a preference row for each individual account
+            for (Account account : accounts) {
+                String key = AccountPreference.buildKey(account);
+                AccountPreference preference = (AccountPreference) mPreferences.getOrDefault(key,
+                        new AccountPreference(getContext(), account, label, icon));
+                preference.setOnPreferenceClickListener(
+                        (Preference pref) -> onAccountPreferenceClicked((AccountPreference) pref));
+
+                accountPreferences.add(preference);
+                preferencesToRemove.remove(key);
+            }
+            mAuthenticatorHelper.preloadDrawableForType(getContext(), accountType);
+        }
+
+        // If there are no accounts, return the "no account added" preference.
+        if (accountPreferences.isEmpty()) {
+            preferencesToRemove.remove(NO_ACCOUNT_PREF_KEY);
+            return Arrays.asList(mPreferences.getOrDefault(NO_ACCOUNT_PREF_KEY,
+                    createNoAccountsAddedPreference()));
+        }
+
+        Collections.sort(accountPreferences, Comparator.comparing(
+                (AccountPreference a) -> a.getSummary().toString())
+                .thenComparing((AccountPreference a) -> a.getTitle().toString()));
+
+        return accountPreferences;
+    }
+
+    private Preference createNoAccountsAddedPreference() {
+        Preference emptyPreference = new Preference(getContext());
+        emptyPreference.setTitle(R.string.no_accounts_added);
+        emptyPreference.setKey(NO_ACCOUNT_PREF_KEY);
+        emptyPreference.setSelectable(false);
+
+        return emptyPreference;
+    }
+
+    /**
+     * Returns whether the account type has any of the authorities requested by the caller.
+     *
+     * <p> Derived from {@link AccountPreferenceController#accountTypeHasAnyRequestedAuthorities}
+     */
+    private boolean accountTypeHasAnyRequestedAuthorities(String accountType) {
+        if (mAuthorities == null || mAuthorities.length == 0) {
+            // No authorities required
+            return true;
+        }
+        ArrayList<String> authoritiesForType =
+                mAuthenticatorHelper.getAuthoritiesForAccountType(accountType);
+        if (authoritiesForType == null) {
+            return false;
+        }
+        for (int j = 0; j < mAuthorities.length; j++) {
+            if (authoritiesForType.contains(mAuthorities[j])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static class AccountPreference extends Preference {
+        /** Account that this Preference represents. */
+        private final Account mAccount;
+        private final CharSequence mLabel;
+
+        private AccountPreference(Context context, Account account, CharSequence label,
+                Drawable icon) {
+            super(context);
+            mAccount = account;
+            mLabel = label;
+
+            setKey(buildKey(account));
+            setTitle(account.name);
+            setSummary(label);
+            setIcon(icon);
+        }
+
+        /**
+         * Build a unique preference key based on the account.
+         */
+        public static String buildKey(Account account) {
+            return String.valueOf(account.hashCode());
+        }
+
+        public Account getAccount() {
+            return mAccount;
+        }
+
+        public CharSequence getLabel() {
+            return mLabel;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountManagerHelper.java b/src/com/android/car/settings/accounts/AccountManagerHelper.java
deleted file mode 100644
index c0e56ed..0000000
--- a/src/com/android/car/settings/accounts/AccountManagerHelper.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.accounts;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import com.android.settingslib.accounts.AuthenticatorHelper;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper class for managing accounts that belong to a single user.
- */
-class AccountManagerHelper {
-    private final Context mContext;
-    private final AuthenticatorHelper.OnAccountsUpdateListener mUpdateListener;
-    private final AuthenticatorHelper mAuthenticatorHelper;
-
-    private final UserHandle mCurrentUserHandle;
-
-    public AccountManagerHelper(Context context,
-            AuthenticatorHelper.OnAccountsUpdateListener listener) {
-        mContext = context;
-        mUpdateListener = listener;
-        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mCurrentUserHandle = userManager.getUserInfo(UserHandle.myUserId()).getUserHandle();
-
-        // Listen to account updates for this user.
-        mAuthenticatorHelper =
-                new AuthenticatorHelper(mContext, mCurrentUserHandle, mUpdateListener);
-    }
-
-    /**
-     * Starts listening to account updates. Every registered listener should be unregistered.
-     */
-    public void startListeningToAccountUpdates() {
-        mAuthenticatorHelper.listenToAccountUpdates();
-    }
-
-    /**
-     * Stops listening to account updates. Should be called on cleanup/destroy.
-     */
-    public void stopListeningToAccountUpdates() {
-        mAuthenticatorHelper.stopListeningToAccountUpdates();
-    }
-
-    /**
-     * Returns all the enabled accounts that exist for the current user. These include 1st and 3rd
-     * party accounts.
-     *
-     * @return List of {@code Account} for the currently logged in user.
-     */
-    public List<Account> getAccountsForCurrentUser() {
-        List<Account> accounts = new ArrayList<>();
-
-        String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
-        for (int i = 0; i < accountTypes.length; i++) {
-            String accountType = accountTypes[i];
-            Account[] accountsForType = AccountManager.get(mContext)
-                    .getAccountsByTypeAsUser(accountType, mCurrentUserHandle);
-            for (Account account : accountsForType) {
-                accounts.add(account);
-            }
-        }
-        return accounts;
-    }
-
-    /**
-     * Returns whether the given account is in the list of accounts for the current user.
-     * Useful for checking whether an account has been deleted.
-     *
-     * @param account Account which existence we're checking for.
-     * @return {@code true} if it exists, {@code false} if it doesn't.
-     */
-    public boolean accountExists(Account account) {
-        if (account == null) {
-            return false;
-        }
-
-        Account[] accounts = AccountManager.get(mContext).getAccountsByTypeAsUser(
-                account.type, mCurrentUserHandle);
-        for (Account other : accounts) {
-            if (other.equals(account)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Wrapper for {@code AuthenticatorHelper.getDrawableForType}.
-     * Gets an icon associated with a particular account type.
-     *
-     * @param accountType the type of account
-     * @return a drawable for the icon
-     */
-    public Drawable getDrawableForType(final String accountType) {
-        return mAuthenticatorHelper.getDrawableForType(mContext, accountType);
-    }
-
-    /**
-     * Wrapper for {@code AuthenticatorHelper.getLabelForType}.
-     * Gets the label associated with a particular account type. If none found, return {@code null}.
-     *
-     * @param accountType the type of account
-     * @return a CharSequence for the label or null if one cannot be found.
-     */
-    public CharSequence getLabelForType(final String accountType) {
-        return mAuthenticatorHelper.getLabelForType(mContext, accountType);
-    }
-}
diff --git a/src/com/android/car/settings/accounts/AccountSettingsFragment.java b/src/com/android/car/settings/accounts/AccountSettingsFragment.java
new file mode 100644
index 0000000..4d3212e
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountSettingsFragment.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Lists the user's accounts and any related options.
+ */
+public class AccountSettingsFragment extends SettingsFragment {
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.account_settings_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Enable the add account button if the user is allowed to modify accounts
+        Button addAccountButton = requireActivity().findViewById(R.id.action_button1);
+        if (new CarUserManagerHelper(getContext()).canCurrentProcessModifyAccounts()) {
+            addAccountButton.setText(R.string.user_add_account_menu);
+            addAccountButton.setOnClickListener(v -> onAddAccountClicked());
+        } else {
+            addAccountButton.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        String[] authorities = getActivity().getIntent().getStringArrayExtra(
+                Settings.EXTRA_AUTHORITIES);
+        if (authorities != null) {
+            use(AccountListPreferenceController.class, R.string.pk_account_list)
+                    .setAuthorities(authorities);
+        }
+    }
+
+    private void onAddAccountClicked() {
+        launchFragment(new ChooseAccountFragment());
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountSyncDetailsFragment.java b/src/com/android/car/settings/accounts/AccountSyncDetailsFragment.java
new file mode 100644
index 0000000..65f2bbc
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountSyncDetailsFragment.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncAdapterType;
+import android.content.SyncStatusObserver;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.Set;
+
+/**
+ * Shows details for syncing an account.
+ */
+public class AccountSyncDetailsFragment extends SettingsFragment {
+    private static final String EXTRA_ACCOUNT = "extra_account";
+    private static final String EXTRA_USER_HANDLE = "extra_user_handle";
+    private boolean mIsStarted = false;
+    private Object mStatusChangeListenerHandle;
+    private SyncStatusObserver mSyncStatusObserver =
+            which -> ThreadUtils.postOnMainThread(() -> {
+                // The observer call may occur even if the fragment hasn't been started, so
+                // only force an update if the fragment hasn't been stopped.
+                if (mIsStarted) {
+                    updateSyncButton();
+                }
+            });
+
+    /**
+     * Creates a new AccountSyncDetailsFragment.
+     *
+     * <p>Passes the provided account and user handle to the fragment via fragment arguments.
+     */
+    public static AccountSyncDetailsFragment newInstance(Account account, UserHandle userHandle) {
+        AccountSyncDetailsFragment accountSyncDetailsFragment = new AccountSyncDetailsFragment();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EXTRA_ACCOUNT, account);
+        bundle.putParcelable(EXTRA_USER_HANDLE, userHandle);
+        accountSyncDetailsFragment.setArguments(bundle);
+        return accountSyncDetailsFragment;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.account_sync_details_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        Account account = getArguments().getParcelable(EXTRA_ACCOUNT);
+        UserHandle userHandle = getArguments().getParcelable(EXTRA_USER_HANDLE);
+
+        use(AccountDetailsWithSyncStatusPreferenceController.class,
+                R.string.pk_account_details_with_sync)
+                .setAccount(account);
+        use(AccountDetailsWithSyncStatusPreferenceController.class,
+                R.string.pk_account_details_with_sync)
+                .setUserHandle(userHandle);
+
+        use(AccountSyncDetailsPreferenceController.class, R.string.pk_account_sync_details)
+                .setAccount(account);
+        use(AccountSyncDetailsPreferenceController.class, R.string.pk_account_sync_details)
+                .setUserHandle(userHandle);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        updateSyncButton();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mIsStarted = true;
+        mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener(
+                ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
+                        | ContentResolver.SYNC_OBSERVER_TYPE_STATUS
+                        | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, mSyncStatusObserver);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mIsStarted = false;
+        if (mStatusChangeListenerHandle != null) {
+            ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
+        }
+    }
+
+    private void updateSyncButton() {
+        // Set the action button to either request or cancel sync, depending on the current state
+        Button syncButton = requireActivity().findViewById(R.id.action_button1);
+
+        UserHandle userHandle = getArguments().getParcelable(EXTRA_USER_HANDLE);
+        boolean hasActiveSyncs = !ContentResolver.getCurrentSyncsAsUser(
+                userHandle.getIdentifier()).isEmpty();
+
+        // If there are active syncs, clicking the button with cancel them. Otherwise, clicking the
+        // button will start them.
+        syncButton.setText(
+                hasActiveSyncs ? R.string.sync_button_sync_cancel : R.string.sync_button_sync_now);
+        syncButton.setOnClickListener(v -> {
+            if (hasActiveSyncs) {
+                cancelSyncForEnabledProviders();
+            } else {
+                requestSyncForEnabledProviders();
+            }
+        });
+    }
+
+    private void requestSyncForEnabledProviders() {
+        Account account = getArguments().getParcelable(EXTRA_ACCOUNT);
+        UserHandle userHandle = getArguments().getParcelable(EXTRA_USER_HANDLE);
+        int userId = userHandle.getIdentifier();
+
+        Set<SyncAdapterType> adapters = AccountSyncHelper.getSyncableSyncAdaptersForAccount(account,
+                userHandle);
+        for (SyncAdapterType adapter : adapters) {
+            AccountSyncHelper.requestSyncIfAllowed(account, adapter.authority, userId);
+        }
+    }
+
+    private void cancelSyncForEnabledProviders() {
+        Account account = getArguments().getParcelable(EXTRA_ACCOUNT);
+        UserHandle userHandle = getArguments().getParcelable(EXTRA_USER_HANDLE);
+        int userId = userHandle.getIdentifier();
+
+        Set<SyncAdapterType> adapters = AccountSyncHelper.getSyncableSyncAdaptersForAccount(account,
+                userHandle);
+        for (SyncAdapterType adapter : adapters) {
+            ContentResolver.cancelSyncAsUser(account, adapter.authority, userId);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountSyncDetailsPreferenceController.java b/src/com/android/car/settings/accounts/AccountSyncDetailsPreferenceController.java
new file mode 100644
index 0000000..435bbf6
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountSyncDetailsPreferenceController.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SyncAdapterType;
+import android.content.SyncInfo;
+import android.content.SyncStatusInfo;
+import android.content.SyncStatusObserver;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.text.format.DateFormat;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.collection.ArrayMap;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.accounts.AuthenticatorHelper;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Controller that presents all visible sync adapters for an account.
+ *
+ * <p>Largely derived from {@link com.android.settings.accounts.AccountSyncSettings}.
+ */
+public class AccountSyncDetailsPreferenceController extends
+        PreferenceController<PreferenceGroup> implements
+        AuthenticatorHelper.OnAccountsUpdateListener {
+    private static final Logger LOG = new Logger(AccountSyncDetailsPreferenceController.class);
+    /**
+     * Preferences are keyed by authority so that existing SyncPreferences can be reused on account
+     * sync.
+     */
+    private final Map<String, SyncPreference> mSyncPreferences = new ArrayMap<>();
+    private boolean mIsStarted = false;
+    private Account mAccount;
+    private UserHandle mUserHandle;
+    private AuthenticatorHelper mAuthenticatorHelper;
+    private Object mStatusChangeListenerHandle;
+    private SyncStatusObserver mSyncStatusObserver =
+            which -> ThreadUtils.postOnMainThread(() -> {
+                // The observer call may occur even if the fragment hasn't been started, so
+                // only force an update if the fragment hasn't been stopped.
+                if (mIsStarted) {
+                    forceUpdateSyncCategory();
+                }
+            });
+
+    public AccountSyncDetailsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /** Sets the account that the sync preferences are being shown for. */
+    public void setAccount(Account account) {
+        mAccount = account;
+    }
+
+    /** Sets the user handle used by the controller. */
+    public void setUserHandle(UserHandle userHandle) {
+        mUserHandle = userHandle;
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /**
+     * Verifies that the controller was properly initialized with {@link #setAccount(Account)} and
+     * {@link #setUserHandle(UserHandle)}.
+     *
+     * @throws IllegalStateException if the account or user handle is {@code null}
+     */
+    @Override
+    protected void checkInitialized() {
+        LOG.v("checkInitialized");
+        if (mAccount == null) {
+            throw new IllegalStateException(
+                    "AccountSyncDetailsPreferenceController must be initialized by calling "
+                            + "setAccount(Account)");
+        }
+        if (mUserHandle == null) {
+            throw new IllegalStateException(
+                    "AccountSyncDetailsPreferenceController must be initialized by calling "
+                            + "setUserHandle(UserHandle)");
+        }
+    }
+
+    /**
+     * Initializes the authenticator helper.
+     */
+    @Override
+    protected void onCreateInternal() {
+        mAuthenticatorHelper = new AuthenticatorHelper(getContext(), mUserHandle, /* listener= */
+                this);
+    }
+
+    /**
+     * Registers the account update and sync status change callbacks.
+     */
+    @Override
+    protected void onStartInternal() {
+        mIsStarted = true;
+        mAuthenticatorHelper.listenToAccountUpdates();
+
+        mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener(
+                ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
+                        | ContentResolver.SYNC_OBSERVER_TYPE_STATUS
+                        | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, mSyncStatusObserver);
+    }
+
+    /**
+     * Unregisters the account update and sync status change callbacks.
+     */
+    @Override
+    protected void onStopInternal() {
+        mIsStarted = false;
+        mAuthenticatorHelper.stopListeningToAccountUpdates();
+        if (mStatusChangeListenerHandle != null) {
+            ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
+        }
+    }
+
+    @Override
+    public void onAccountsUpdate(UserHandle userHandle) {
+        // Only force a refresh if accounts have changed for the current user.
+        if (userHandle.equals(mUserHandle)) {
+            forceUpdateSyncCategory();
+        }
+    }
+
+    @Override
+    public void updateState(PreferenceGroup preferenceGroup) {
+        // Add preferences for each account if the controller should be available
+        forceUpdateSyncCategory();
+    }
+
+    /**
+     * Handles toggling/syncing when a sync preference is clicked on.
+     *
+     * <p>Largely derived from
+     * {@link com.android.settings.accounts.AccountSyncSettings#onPreferenceTreeClick}.
+     */
+    private boolean onSyncPreferenceClicked(SyncPreference preference) {
+        String authority = preference.getKey();
+        String packageName = preference.getPackageName();
+        int uid = preference.getUid();
+        if (preference.isOneTimeSyncMode()) {
+            // If the sync adapter doesn't have access to the account we either
+            // request access by starting an activity if possible or kick off the
+            // sync which will end up posting an access request notification.
+            if (requestAccountAccessIfNeeded(packageName, uid)) {
+                return true;
+            }
+            requestSync(authority);
+        } else {
+            boolean syncOn = preference.isChecked();
+            int userId = mUserHandle.getIdentifier();
+            boolean oldSyncState = ContentResolver.getSyncAutomaticallyAsUser(mAccount,
+                    authority, userId);
+            if (syncOn != oldSyncState) {
+                // Toggling this switch triggers sync but we may need a user approval. If the
+                // sync adapter doesn't have access to the account we either request access by
+                // starting an activity if possible or kick off the sync which will end up
+                // posting an access request notification.
+                if (syncOn && requestAccountAccessIfNeeded(packageName, uid)) {
+                    return true;
+                }
+                // If we're enabling sync, this will request a sync as well.
+                ContentResolver.setSyncAutomaticallyAsUser(mAccount, authority, syncOn, userId);
+                if (syncOn) {
+                    requestSync(authority);
+                } else {
+                    cancelSync(authority);
+                }
+            }
+        }
+        return true;
+    }
+
+    private void requestSync(String authority) {
+        AccountSyncHelper.requestSyncIfAllowed(mAccount, authority, mUserHandle.getIdentifier());
+    }
+
+    private void cancelSync(String authority) {
+        ContentResolver.cancelSyncAsUser(mAccount, authority, mUserHandle.getIdentifier());
+    }
+
+    /**
+     * Requests account access if needed.
+     *
+     * <p>Copied from
+     * {@link com.android.settings.accounts.AccountSyncSettings#requestAccountAccessIfNeeded}.
+     */
+    private boolean requestAccountAccessIfNeeded(String packageName, int uid) {
+        if (packageName == null) {
+            return false;
+        }
+
+        AccountManager accountManager = getContext().getSystemService(AccountManager.class);
+        if (!accountManager.hasAccountAccess(mAccount, packageName, mUserHandle)) {
+            IntentSender intent = accountManager.createRequestAccountAccessIntentSenderAsUser(
+                    mAccount, packageName, mUserHandle);
+            if (intent != null) {
+                try {
+                    getFragmentController().startIntentSenderForResult(intent,
+                            uid, /* fillInIntent= */ null, /* flagsMask= */0,
+                            /* flagsValues= */0, /* options= */null,
+                            this::onAccountRequestApproved);
+                    return true;
+                } catch (IntentSender.SendIntentException e) {
+                    LOG.e("Error requesting account access", e);
+                }
+            }
+        }
+        return false;
+    }
+
+    /** Handles a sync adapter refresh when an account request was approved. */
+    public void onAccountRequestApproved(int uid, int resultCode, @Nullable Intent data) {
+        if (resultCode == Activity.RESULT_OK) {
+            for (SyncPreference pref : mSyncPreferences.values()) {
+                if (pref.getUid() == uid) {
+                    onSyncPreferenceClicked(pref);
+                    return;
+                }
+            }
+        }
+    }
+
+    /** Forces a refresh of the sync adapter preferences. */
+    private void forceUpdateSyncCategory() {
+        Set<String> preferencesToRemove = new HashSet<>(mSyncPreferences.keySet());
+        List<SyncPreference> preferences = getSyncPreferences(preferencesToRemove);
+
+        // Sort the preferences, add the ones that need to be added, and remove the ones that need
+        // to be removed. Manually set the order so that existing preferences are reordered
+        // correctly.
+        Collections.sort(preferences, Comparator.comparing(
+                (SyncPreference a) -> a.getTitle().toString())
+                .thenComparing((SyncPreference a) -> a.getSummary().toString()));
+
+        for (int i = 0; i < preferences.size(); i++) {
+            SyncPreference pref = preferences.get(i);
+            pref.setOrder(i);
+            mSyncPreferences.put(pref.getKey(), pref);
+            getPreference().addPreference(pref);
+        }
+
+        for (String key : preferencesToRemove) {
+            getPreference().removePreference(mSyncPreferences.get(key));
+            mSyncPreferences.remove(key);
+        }
+    }
+
+    /**
+     * Returns a list of preferences corresponding to the visible sync adapters for the current
+     * user.
+     *
+     * <p> Derived from {@link com.android.settings.accounts.AccountSyncSettings#setFeedsState}
+     * and {@link com.android.settings.accounts.AccountSyncSettings#updateAccountSwitches}.
+     *
+     * @param preferencesToRemove the keys for the preferences currently being shown; only the keys
+     *                            for preferences to be removed will remain after method execution
+     */
+    private List<SyncPreference> getSyncPreferences(Set<String> preferencesToRemove) {
+        int userId = mUserHandle.getIdentifier();
+        PackageManager packageManager = getContext().getPackageManager();
+        List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
+        // Whether one time sync is enabled rather than automtic sync
+        boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(userId);
+
+        List<SyncPreference> syncPreferences = new ArrayList<>();
+
+        Set<SyncAdapterType> syncAdapters = AccountSyncHelper.getVisibleSyncAdaptersForAccount(
+                getContext(), mAccount, mUserHandle);
+        for (SyncAdapterType syncAdapter : syncAdapters) {
+            String authority = syncAdapter.authority;
+
+            int uid;
+            try {
+                uid = packageManager.getPackageUidAsUser(syncAdapter.getPackageName(), userId);
+            } catch (PackageManager.NameNotFoundException e) {
+                LOG.e("No uid for package" + syncAdapter.getPackageName(), e);
+                // If we can't get the Uid for the package hosting the sync adapter, don't show it
+                continue;
+            }
+
+            // If we've reached this point, the sync adapter should be shown. If a preference for
+            // the sync adapter already exists, update its state. Otherwise, create a new
+            // preference.
+            SyncPreference pref = mSyncPreferences.getOrDefault(authority,
+                    new SyncPreference(getContext(), authority));
+            pref.setUid(uid);
+            pref.setPackageName(syncAdapter.getPackageName());
+            pref.setOnPreferenceClickListener(
+                    (Preference p) -> onSyncPreferenceClicked((SyncPreference) p));
+
+            CharSequence title = AccountSyncHelper.getTitle(getContext(), authority, mUserHandle);
+            pref.setTitle(title);
+
+            // Keep track of preferences that need to be added and removed
+            syncPreferences.add(pref);
+            preferencesToRemove.remove(authority);
+
+            SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(mAccount, authority,
+                    userId);
+            boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(mAccount, authority,
+                    userId);
+            boolean activelySyncing = AccountSyncHelper.isSyncing(mAccount, currentSyncs,
+                    authority);
+
+            // The preference should be checked if one one-time sync or regular sync is enabled
+            boolean checked = oneTimeSyncMode || syncEnabled;
+            pref.setChecked(checked);
+
+            String summary = getSummary(status, syncEnabled, activelySyncing);
+            pref.setSummary(summary);
+
+            // Update the sync state so the icon is updated
+            AccountSyncHelper.SyncState syncState = AccountSyncHelper.getSyncState(status,
+                    syncEnabled, activelySyncing);
+            pref.setSyncState(syncState);
+            pref.setOneTimeSyncMode(oneTimeSyncMode);
+        }
+
+        return syncPreferences;
+    }
+
+    private String getSummary(SyncStatusInfo status, boolean syncEnabled, boolean activelySyncing) {
+        long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
+        // Set the summary based on the current syncing state
+        if (!syncEnabled) {
+            return getContext().getString(R.string.sync_disabled);
+        } else if (activelySyncing) {
+            return getContext().getString(R.string.sync_in_progress);
+        } else if (successEndTime != 0) {
+            Date date = new Date();
+            date.setTime(successEndTime);
+            String timeString = formatSyncDate(date);
+            return getContext().getString(R.string.last_synced, timeString);
+        }
+        return "";
+    }
+
+    @VisibleForTesting
+    String formatSyncDate(Date date) {
+        return DateFormat.getDateFormat(getContext()).format(date) + " " + DateFormat.getTimeFormat(
+                getContext()).format(date);
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountSyncHelper.java b/src/com/android/car/settings/accounts/AccountSyncHelper.java
new file mode 100644
index 0000000..109e0d4
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountSyncHelper.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncAdapterType;
+import android.content.SyncInfo;
+import android.content.SyncStatusInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.car.settings.common.Logger;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Helper that provides utility methods for account syncing. */
+class AccountSyncHelper {
+    private static final Logger LOG = new Logger(AccountSyncHelper.class);
+
+    private AccountSyncHelper() {
+    }
+
+    /** Returns the visible sync adapters available for an account. */
+    static Set<SyncAdapterType> getVisibleSyncAdaptersForAccount(Context context, Account account,
+            UserHandle userHandle) {
+        Set<SyncAdapterType> syncableAdapters = getSyncableSyncAdaptersForAccount(account,
+                userHandle);
+
+        syncableAdapters.removeIf(
+                (SyncAdapterType syncAdapter) -> !isVisible(context, syncAdapter, userHandle));
+
+        return syncableAdapters;
+    }
+
+    /** Returns the syncable sync adapters available for an account. */
+    static Set<SyncAdapterType> getSyncableSyncAdaptersForAccount(Account account,
+            UserHandle userHandle) {
+        Set<SyncAdapterType> adapters = new HashSet<>();
+
+        SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+                userHandle.getIdentifier());
+        for (int i = 0; i < syncAdapters.length; i++) {
+            SyncAdapterType syncAdapter = syncAdapters[i];
+            String authority = syncAdapter.authority;
+
+            // If the sync adapter is not for this account type, don't include it
+            if (!syncAdapter.accountType.equals(account.type)) {
+                continue;
+            }
+
+            boolean isSyncable = ContentResolver.getIsSyncableAsUser(account, authority,
+                    userHandle.getIdentifier()) > 0;
+            // If the adapter is not syncable, don't include it
+            if (!isSyncable) {
+                continue;
+            }
+
+            adapters.add(syncAdapter);
+        }
+
+        return adapters;
+    }
+
+    /**
+     * Requests a sync if it is allowed.
+     *
+     * <p>Derived from
+     * {@link com.android.settings.accounts.AccountSyncSettings#requestOrCancelSync}.
+     */
+    static void requestSyncIfAllowed(Account account, String authority, int userId) {
+        if (!syncIsAllowed(account, authority, userId)) {
+            return;
+        }
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+        ContentResolver.requestSyncAsUser(account, authority, userId, extras);
+    }
+
+    /**
+     * Returns the label for a given sync authority.
+     *
+     * @return the title if available, and an empty CharSequence otherwise
+     */
+    static CharSequence getTitle(Context context, String authority, UserHandle userHandle) {
+        PackageManager packageManager = context.getPackageManager();
+        ProviderInfo providerInfo = packageManager.resolveContentProviderAsUser(
+                authority, /* flags= */ 0, userHandle.getIdentifier());
+        if (providerInfo == null) {
+            return "";
+        }
+
+        return providerInfo.loadLabel(packageManager);
+    }
+
+    /** Returns whether a sync adapter is currently syncing for the account being shown. */
+    static boolean isSyncing(Account account, List<SyncInfo> currentSyncs, String authority) {
+        for (SyncInfo syncInfo : currentSyncs) {
+            if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Returns the current sync state based on sync status information. */
+    static SyncState getSyncState(SyncStatusInfo status, boolean syncEnabled,
+            boolean activelySyncing) {
+        boolean initialSync = status != null && status.initialize;
+        boolean syncIsPending = status != null && status.pending;
+        boolean lastSyncFailed = syncEnabled && status != null && status.lastFailureTime != 0
+                && status.getLastFailureMesgAsInt(0)
+                != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
+        if (activelySyncing && !initialSync) {
+            return SyncState.ACTIVE;
+        } else if (syncIsPending && !initialSync) {
+            return SyncState.PENDING;
+        } else if (lastSyncFailed) {
+            return SyncState.FAILED;
+        }
+        return SyncState.NONE;
+    }
+
+    private static boolean syncIsAllowed(Account account, String authority, int userId) {
+        boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(userId);
+        boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
+                userId);
+        return oneTimeSyncMode || syncEnabled;
+    }
+
+    private static boolean isVisible(Context context, SyncAdapterType syncAdapter,
+            UserHandle userHandle) {
+        String authority = syncAdapter.authority;
+
+        if (!syncAdapter.isUserVisible()) {
+            // If the sync adapter is not visible, don't show it
+            return false;
+        }
+
+        try {
+            context.getPackageManager().getPackageUidAsUser(syncAdapter.getPackageName(),
+                    userHandle.getIdentifier());
+        } catch (PackageManager.NameNotFoundException e) {
+            LOG.e("No uid for package" + syncAdapter.getPackageName(), e);
+            // If we can't get the Uid for the package hosting the sync adapter, don't show it
+            return false;
+        }
+
+        CharSequence title = getTitle(context, authority, userHandle);
+        if (TextUtils.isEmpty(title)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /** Denotes a sync adapter state. */
+    public enum SyncState {
+        /** The sync adapter is actively syncing. */
+        ACTIVE,
+        /** The sync adapter is waiting to start syncing. */
+        PENDING,
+        /** The sync adapter's last attempt to sync failed. */
+        FAILED,
+        /** Nothing to note about the sync adapter's sync state. */
+        NONE;
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountSyncPreferenceController.java b/src/com/android/car/settings/accounts/AccountSyncPreferenceController.java
new file mode 100644
index 0000000..54e6fd0
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountSyncPreferenceController.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.accounts.Account;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncAdapterType;
+import android.os.UserHandle;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller for the account syncing preference.
+ *
+ * <p>Largely derived from {@link com.android.settings.accounts.AccountSyncPreferenceController}.
+ */
+public class AccountSyncPreferenceController extends PreferenceController<Preference> {
+    private static final Logger LOG = new Logger(AccountSyncPreferenceController.class);
+    private Account mAccount;
+    private UserHandle mUserHandle;
+
+    public AccountSyncPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /** Sets the account that the sync preferences are being shown for. */
+    public void setAccount(Account account) {
+        mAccount = account;
+    }
+
+    /** Sets the user handle used by the controller. */
+    public void setUserHandle(UserHandle userHandle) {
+        mUserHandle = userHandle;
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /**
+     * Verifies that the controller was properly initialized with
+     * {@link #setAccount(Account)} and {@link #setUserHandle(UserHandle)}.
+     *
+     * @throws IllegalStateException if the account is {@code null}
+     */
+    @Override
+    protected void checkInitialized() {
+        LOG.v("checkInitialized");
+        if (mAccount == null) {
+            throw new IllegalStateException(
+                    "AccountSyncPreferenceController must be initialized by calling "
+                            + "setAccount(Account)");
+        }
+        if (mUserHandle == null) {
+            throw new IllegalStateException(
+                    "AccountSyncPreferenceController must be initialized by calling "
+                            + "setUserHandle(UserHandle)");
+        }
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(getSummary());
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        getFragmentController().launchFragment(
+                AccountSyncDetailsFragment.newInstance(mAccount, mUserHandle));
+        return true;
+    }
+
+    private CharSequence getSummary() {
+        int userId = mUserHandle.getIdentifier();
+        SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId);
+        int total = 0;
+        int enabled = 0;
+        if (syncAdapters != null) {
+            for (int i = 0, n = syncAdapters.length; i < n; i++) {
+                SyncAdapterType sa = syncAdapters[i];
+                // If the sync adapter isn't for this account type or if the user is not visible,
+                // don't show it.
+                if (!sa.accountType.equals(mAccount.type) || !sa.isUserVisible()) {
+                    continue;
+                }
+                int syncState =
+                        ContentResolver.getIsSyncableAsUser(mAccount, sa.authority, userId);
+                if (syncState > 0) {
+                    // If the sync adapter is syncable, add it to the count of items that can be
+                    // synced.
+                    total++;
+
+                    // If sync is enabled for the sync adapter at the master level or at the account
+                    // level, add it to the count of items that are enabled.
+                    boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(
+                            mAccount, sa.authority, userId);
+                    boolean oneTimeSyncMode =
+                            !ContentResolver.getMasterSyncAutomaticallyAsUser(userId);
+                    if (oneTimeSyncMode || syncEnabled) {
+                        enabled++;
+                    }
+                }
+            }
+        }
+        if (enabled == 0) {
+            return getContext().getText(R.string.account_sync_summary_all_off);
+        } else if (enabled == total) {
+            return getContext().getText(R.string.account_sync_summary_all_on);
+        } else {
+            return getContext().getString(R.string.account_sync_summary_some_on, enabled, total);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountsEntryPreferenceController.java b/src/com/android/car/settings/accounts/AccountsEntryPreferenceController.java
new file mode 100644
index 0000000..1133a9f
--- /dev/null
+++ b/src/com/android/car/settings/accounts/AccountsEntryPreferenceController.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller which determines if the top level entry into Account settings should be displayed
+ * based on the user status.
+ */
+public class AccountsEntryPreferenceController extends PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public AccountsEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return mCarUserManagerHelper.canCurrentProcessModifyAccounts() ? AVAILABLE
+                : DISABLED_FOR_USER;
+    }
+}
diff --git a/src/com/android/car/settings/accounts/AccountsItemProvider.java b/src/com/android/car/settings/accounts/AccountsItemProvider.java
deleted file mode 100644
index 9668c49..0000000
--- a/src/com/android/car/settings/accounts/AccountsItemProvider.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.accounts;
-
-import android.accounts.Account;
-import android.car.user.CarUserManagerHelper;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Implementation of {@link ListItemProvider} for {@link AccountsListFragment}.
- * Creates items that represent current user's accounts.
- */
-class AccountsItemProvider extends ListItemProvider {
-    private final List<ListItem> mItems = new ArrayList<>();
-    private final Context mContext;
-    private final AccountClickListener mItemClickListener;
-    private final CarUserManagerHelper mCarUserManagerHelper;
-    private final AccountManagerHelper mAccountManagerHelper;
-
-    AccountsItemProvider(Context context, AccountClickListener itemClickListener,
-            CarUserManagerHelper carUserManagerHelper, AccountManagerHelper accountManagerHelper) {
-        mContext = context;
-        mItemClickListener = itemClickListener;
-        mCarUserManagerHelper = carUserManagerHelper;
-        mAccountManagerHelper = accountManagerHelper;
-        refreshItems();
-    }
-
-    @Override
-    public ListItem get(int position) {
-        return mItems.get(position);
-    }
-
-    @Override
-    public int size() {
-        return mItems.size();
-    }
-
-    /**
-     * Clears and recreates the list of items.
-     */
-    public void refreshItems() {
-        mItems.clear();
-
-        UserInfo currUserInfo = mCarUserManagerHelper.getCurrentProcessUserInfo();
-
-        List<Account> accounts = getSortedUserAccounts();
-
-        // Only add account-related items if the User can Modify Accounts
-        if (mCarUserManagerHelper.canCurrentProcessModifyAccounts()) {
-            // Add "Account for $User" title for a list of accounts.
-            mItems.add(createSubtitleItem(
-                    mContext.getString(R.string.account_list_title, currUserInfo.name),
-                    accounts.isEmpty() ? mContext.getString(R.string.no_accounts_added) : ""));
-
-            // Add an item for each account owned by the current user (1st and 3rd party accounts)
-            for (Account account : accounts) {
-                mItems.add(createAccountItem(account, account.type, currUserInfo));
-            }
-        }
-    }
-
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    List<Account> getSortedUserAccounts() {
-        List<Account> accounts = mAccountManagerHelper.getAccountsForCurrentUser();
-
-        // Sort accounts
-        Collections.sort(accounts, Comparator.comparing(
-                (Account a) -> mAccountManagerHelper.getLabelForType(a.type).toString())
-                .thenComparing(a -> a.name));
-
-        return accounts;
-    }
-
-    // Creates a subtitle line for visual separation in the list
-    private ListItem createSubtitleItem(String title, String body) {
-        TextListItem item = new TextListItem(mContext);
-        item.setTitle(title);
-        item.setBody(body);
-        item.addViewBinder(viewHolder ->
-                viewHolder.getTitle().setTextAppearance(R.style.SettingsListHeader));
-        // Hiding the divider after subtitle, since subtitle is a header for a group of items.
-        item.setHideDivider(true);
-        return item;
-    }
-
-    // Creates a line for an account that belongs to a given user
-    private ListItem createAccountItem(Account account, String accountType,
-            UserInfo userInfo) {
-        TextListItem item = new TextListItem(mContext);
-        item.setPrimaryActionIcon(mAccountManagerHelper.getDrawableForType(accountType),
-                /* useLargeIcon= */ false);
-        item.setTitle(account.name);
-
-        // Set item body = account label.
-        CharSequence itemBody = mAccountManagerHelper.getLabelForType(accountType);
-        if (!TextUtils.isEmpty(itemBody)) {
-            item.setBody(itemBody.toString());
-        }
-
-        item.setOnClickListener(view -> mItemClickListener.onAccountClicked(account, userInfo));
-
-        // setHideDivider = true will hide the divider to group the items together visually.
-        // All of those without a divider between them will be part of the same "group".
-        item.setHideDivider(true);
-        return item;
-    }
-
-    /**
-     * Interface for registering clicks on account items.
-     */
-    interface AccountClickListener {
-        /**
-         * Invoked when a specific account is clicked on.
-         *
-         * @param account  Account for which to display details.
-         * @param userInfo User who's the owner of the account.
-         */
-        void onAccountClicked(Account account, UserInfo userInfo);
-    }
-}
diff --git a/src/com/android/car/settings/accounts/AccountsListFragment.java b/src/com/android/car/settings/accounts/AccountsListFragment.java
deleted file mode 100644
index c1e3766..0000000
--- a/src/com/android/car/settings/accounts/AccountsListFragment.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.accounts;
-
-import android.accounts.Account;
-import android.car.user.CarUserManagerHelper;
-import android.content.pm.UserInfo;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.car.widget.ListItemProvider;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.settingslib.accounts.AuthenticatorHelper;
-
-/**
- * Shows current user and the accounts that belong to the user.
- */
-public class AccountsListFragment extends ListItemSettingsFragment
-        implements AuthenticatorHelper.OnAccountsUpdateListener,
-        CarUserManagerHelper.OnUsersUpdateListener,
-        AccountsItemProvider.AccountClickListener {
-    private AccountsItemProvider mItemProvider;
-    private AccountManagerHelper mAccountManagerHelper;
-    private CarUserManagerHelper mCarUserManagerHelper;
-
-    private Button mAddAccountButton;
-
-    /**
-     * Creates new instance of CurrentUserDetailsFragment.
-     */
-    public static AccountsListFragment newInstance() {
-        AccountsListFragment accountsFragment = new AccountsListFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.accounts_settings_title);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        accountsFragment.setArguments(bundle);
-        return accountsFragment;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        mAccountManagerHelper = new AccountManagerHelper(getContext(), this);
-        mAccountManagerHelper.startListeningToAccountUpdates();
-
-        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
-        mItemProvider = new AccountsItemProvider(getContext(), this,
-                mCarUserManagerHelper, mAccountManagerHelper);
-
-        // Register to receive changes to the users.
-        mCarUserManagerHelper.registerOnUsersUpdateListener(this);
-
-        // Super class's onActivityCreated need to be called after mContext is initialized.
-        // Because getLineItems is called in there.
-        super.onActivityCreated(savedInstanceState);
-
-        mAddAccountButton = (Button) getActivity().findViewById(R.id.action_button1);
-        if (mCarUserManagerHelper.canCurrentProcessModifyAccounts()) {
-            mAddAccountButton.setText(R.string.user_add_account_menu);
-            mAddAccountButton.setOnClickListener(v -> onAddAccountClicked());
-        } else {
-            mAddAccountButton.setVisibility(View.GONE);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(this);
-        mAccountManagerHelper.stopListeningToAccountUpdates();
-
-        // The action button may be hidden at some point, so make it visible again
-        mAddAccountButton.setVisibility(View.VISIBLE);
-    }
-
-    @Override
-    public void onAccountsUpdate(UserHandle userHandle) {
-        refreshListItems();
-    }
-
-    @Override
-    public void onUsersUpdate() {
-        refreshListItems();
-    }
-
-    @Override
-    public void onAccountClicked(Account account, UserInfo userInfo) {
-        getFragmentController().launchFragment(
-                AccountDetailsFragment.newInstance(account, userInfo));
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        return mItemProvider;
-    }
-
-    private void refreshListItems() {
-        mItemProvider.refreshItems();
-        refreshList();
-    }
-
-    private void onAddAccountClicked() {
-        getFragmentController().launchFragment(ChooseAccountFragment.newInstance());
-    }
-}
diff --git a/src/com/android/car/settings/accounts/AddAccountActivity.java b/src/com/android/car/settings/accounts/AddAccountActivity.java
index 15910ab..d5ce571 100644
--- a/src/com/android/car/settings/accounts/AddAccountActivity.java
+++ b/src/com/android/car/settings/accounts/AddAccountActivity.java
@@ -24,7 +24,7 @@
 import android.accounts.OperationCanceledException;
 import android.app.Activity;
 import android.app.PendingIntent;
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -37,16 +37,15 @@
 import com.android.car.settings.common.Logger;
 
 import java.io.IOException;
+
 /**
- *
  * Entry point Activity for account setup. Works as follows
  *
  * <ol>
- *   <li> After receiving an account type from ChooseAccountFragment, this Activity launches the
- *        account setup specified by AccountManager.
- *   <li> After the account setup, this Activity finishes without showing anything.
+ * <li> After receiving an account type from ChooseAccountFragment, this Activity launches the
+ * account setup specified by AccountManager.
+ * <li> After the account setup, this Activity finishes without showing anything.
  * </ol>
- *
  */
 public class AddAccountActivity extends Activity {
     /**
@@ -54,11 +53,9 @@
      */
     private static final String KEY_ADD_CALLED = "AddAccountCalled";
     /**
-     *
      * Extra parameter to identify the caller. Applications may display a
      * different UI if the call is made from Settings or from a specific
      * application.
-     *
      */
     private static final String KEY_CALLER_IDENTITY = "pendingIntent";
     private static final String SHOULD_NOT_RESOLVE = "SHOULDN'T RESOLVE!";
@@ -79,11 +76,6 @@
     private PendingIntent mPendingIntent;
     private boolean mAddAccountCalled;
 
-    public boolean hasMultipleUsers(Context context) {
-        return ((UserManager) context.getSystemService(Context.USER_SERVICE))
-                .getUsers().size() > 1;
-    }
-
     private final AccountManagerCallback<Bundle> mCallback = new AccountManagerCallback<Bundle>() {
         @Override
         public void run(AccountManagerFuture<Bundle> future) {
@@ -128,14 +120,8 @@
         }
 
         mCarUserManagerHelper = new CarUserManagerHelper(this);
-
-        if (mAddAccountCalled) {
-            // We already called add account - maybe the callback was lost.
-            finish();
-            return;
-        }
-
         mUserHandle = mCarUserManagerHelper.getCurrentProcessUserInfo().getUserHandle();
+
         if (mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
                 UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
             // We aren't allowed to add an account.
@@ -145,6 +131,12 @@
             finish();
             return;
         }
+
+        if (mAddAccountCalled) {
+            // We already called add account - maybe the callback was lost.
+            finish();
+            return;
+        }
         addAccount(getIntent().getStringExtra(EXTRA_SELECTED_ACCOUNT));
     }
 
@@ -187,4 +179,9 @@
                 mUserHandle);
         mAddAccountCalled = true;
     }
+
+    private boolean hasMultipleUsers(Context context) {
+        return ((UserManager) context.getSystemService(Context.USER_SERVICE))
+                .getUsers().size() > 1;
+    }
 }
diff --git a/src/com/android/car/settings/accounts/ChooseAccountFragment.java b/src/com/android/car/settings/accounts/ChooseAccountFragment.java
index a65a29b..2f864ec 100644
--- a/src/com/android/car/settings/accounts/ChooseAccountFragment.java
+++ b/src/com/android/car/settings/accounts/ChooseAccountFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -16,54 +16,43 @@
 
 package com.android.car.settings.accounts;
 
-import android.os.Bundle;
-import android.os.UserHandle;
+import android.content.Context;
+import android.provider.Settings;
 
-import androidx.car.widget.ListItemProvider;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.car.settings.common.Logger;
-import com.android.settingslib.accounts.AuthenticatorHelper;
+import com.android.car.settings.common.SettingsFragment;
+
+import java.util.Arrays;
+import java.util.HashSet;
 
 /**
- * Activity asking a user to select an account to be set up.
- *
- * <p>An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER},
- * if the user for which the action needs to be performed is different to the one the
- * Settings App will run in.
+ * Lists accounts the user can add.
  */
-public class ChooseAccountFragment extends ListItemSettingsFragment
-        implements AuthenticatorHelper.OnAccountsUpdateListener {
-    private static final Logger LOG = new Logger(ChooseAccountFragment.class);
-
-    private ChooseAccountItemProvider mItemProvider;
-
-    public static ChooseAccountFragment newInstance() {
-        ChooseAccountFragment
-                chooseAccountFragment = new ChooseAccountFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.add_an_account);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        chooseAccountFragment.setArguments(bundle);
-        return chooseAccountFragment;
+public class ChooseAccountFragment extends SettingsFragment {
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.choose_account_fragment;
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        mItemProvider = new ChooseAccountItemProvider(getContext(), this);
-        super.onActivityCreated(savedInstanceState);
-    }
+    public void onAttach(Context context) {
+        super.onAttach(context);
 
-    @Override
-    public void onAccountsUpdate(UserHandle userHandle) {
-        LOG.v("Accounts changed, refreshing the account list.");
-        mItemProvider.refreshItems();
-        refreshList();
-    }
+        String[] authorities = requireActivity().getIntent().getStringArrayExtra(
+                Settings.EXTRA_AUTHORITIES);
+        if (authorities != null) {
+            use(ChooseAccountPreferenceController.class, R.string.pk_add_account)
+                    .setAuthorities(Arrays.asList(authorities));
+        }
 
-    @Override
-    public ListItemProvider getItemProvider() {
-        return mItemProvider;
+        String[] accountTypesForFilter = requireActivity().getIntent().getStringArrayExtra(
+                Settings.EXTRA_ACCOUNT_TYPES);
+        if (accountTypesForFilter != null) {
+            use(ChooseAccountPreferenceController.class, R.string.pk_add_account)
+                    .setAccountTypesFilter(new HashSet<>(Arrays.asList(accountTypesForFilter)));
+        }
     }
 }
diff --git a/src/com/android/car/settings/accounts/ChooseAccountItemProvider.java b/src/com/android/car/settings/accounts/ChooseAccountItemProvider.java
deleted file mode 100644
index 5a1835a..0000000
--- a/src/com/android/car/settings/accounts/ChooseAccountItemProvider.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.accounts;
-
-import android.accounts.AccountManager;
-import android.accounts.AuthenticatorDescription;
-import android.app.ActivityManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SyncAdapterType;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.common.Logger;
-import com.android.internal.util.CharSequences;
-import com.android.settingslib.accounts.AuthenticatorHelper;
-
-import libcore.util.Nullable;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implementation of ListItemProvider for the ChooseAccountFragment.
- */
-class ChooseAccountItemProvider extends ListItemProvider {
-    private static final Logger LOG = new Logger(ChooseAccountItemProvider.class);
-    private static final String AUTHORITIES_FILTER_KEY = "authorities";
-
-    private final List<ListItem> mItems = new ArrayList<>();
-    private final Context mContext;
-    private final ChooseAccountFragment mFragment;
-    private final UserManager mUserManager;
-    private final ArrayList<ProviderEntry> mProviderList = new ArrayList<>();
-    @Nullable private AuthenticatorDescription[] mAuthDescs;
-    @Nullable private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities;
-    private Map<String, AuthenticatorDescription> mTypeToAuthDescription = new HashMap<>();
-    private final AuthenticatorHelper mAuthenticatorHelper;
-
-    // The UserHandle of the user we are choosing an account for
-    private final UserHandle mUserHandle;
-    private final String[] mAuthorities;
-
-    ChooseAccountItemProvider(Context context, ChooseAccountFragment fragment) {
-        mContext = context;
-        mFragment = fragment;
-        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mUserHandle =
-                mUserManager.getUserInfo(ActivityManager.getCurrentUser()).getUserHandle();
-
-        mAuthorities = fragment.getActivity().getIntent().getStringArrayExtra(
-                AUTHORITIES_FILTER_KEY);
-
-        // create auth helper
-        UserInfo currUserInfo = mUserManager.getUserInfo(ActivityManager.getCurrentUser());
-        mAuthenticatorHelper =
-                new AuthenticatorHelper(mContext, currUserInfo.getUserHandle(), mFragment);
-
-        updateAuthDescriptions();
-        refreshItems();
-    }
-
-    @Override
-    public ListItem get(int position) {
-        return mItems.get(position);
-    }
-
-    @Override
-    public int size() {
-        return mItems.size();
-    }
-
-    /**
-     * Clears the existing item list and rebuilds it with new data.
-     */
-    public void refreshItems() {
-        mItems.clear();
-
-        for (int i = 0; i < mProviderList.size(); i++) {
-            String accountType = mProviderList.get(i).type;
-            Drawable icon = mAuthenticatorHelper.getDrawableForType(mContext, accountType);
-
-            TextListItem item = new TextListItem(mContext);
-            item.setPrimaryActionIcon(icon, /* useLargeIcon= */ false);
-            item.setTitle(mProviderList.get(i).name.toString());
-            item.setOnClickListener(v -> onItemSelected(accountType));
-            mItems.add(item);
-        }
-    }
-
-    // Starts a AddAccountActivity for the accountType that was clicked on.
-    private void onItemSelected(String accountType) {
-        Intent intent = new Intent(mContext, AddAccountActivity.class);
-        intent.putExtra(AddAccountActivity.EXTRA_SELECTED_ACCOUNT, accountType);
-        mContext.startActivity(intent);
-    }
-
-    /**
-     * Updates provider icons.
-     */
-    private void updateAuthDescriptions() {
-        mAuthDescs = AccountManager.get(mContext).getAuthenticatorTypesAsUser(
-                mUserHandle.getIdentifier());
-        for (int i = 0; i < mAuthDescs.length; i++) {
-            mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
-        }
-        onAuthDescriptionsUpdated();
-    }
-
-    private void onAuthDescriptionsUpdated() {
-        // Create list of providers to show on page.
-        for (int i = 0; i < mAuthDescs.length; i++) {
-            String accountType = mAuthDescs[i].type;
-            CharSequence providerName = getLabelForType(accountType);
-
-            // Get the account authorities implemented by the account type.
-            ArrayList<String> accountAuths = getAuthoritiesForAccountType(accountType);
-
-            // If there are specific authorities required, we need to check whether it's
-            // included in the account type.
-            if (mAuthorities != null && mAuthorities.length > 0 && accountAuths != null) {
-                for (int k = 0; k < mAuthorities.length; k++) {
-                    if (accountAuths.contains(mAuthorities[k])) {
-                        mProviderList.add(
-                                new ProviderEntry(providerName, accountType));
-                        break;
-                    }
-                }
-            } else {
-                mProviderList.add(
-                        new ProviderEntry(providerName, accountType));
-            }
-        }
-        Collections.sort(mProviderList);
-    }
-
-    private ArrayList<String> getAuthoritiesForAccountType(String type) {
-        if (mAccountTypeToAuthorities == null) {
-            mAccountTypeToAuthorities = new HashMap<>();
-            SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
-                    mUserHandle.getIdentifier());
-            for (int i = 0, n = syncAdapters.length; i < n; i++) {
-                final SyncAdapterType adapterType = syncAdapters[i];
-                ArrayList<String> authorities =
-                        mAccountTypeToAuthorities.get(adapterType.accountType);
-                if (authorities == null) {
-                    authorities = new ArrayList<String>();
-                    mAccountTypeToAuthorities.put(adapterType.accountType, authorities);
-                }
-                LOG.v("added authority " + adapterType.authority + " to accountType "
-                        + adapterType.accountType);
-                authorities.add(adapterType.authority);
-            }
-        }
-        return mAccountTypeToAuthorities.get(type);
-    }
-
-    /**
-     * Gets the label associated with a particular account type. If none found, return null.
-     *
-     * @param accountType the type of account
-     * @return a CharSequence for the label or null if one cannot be found.
-     */
-    private CharSequence getLabelForType(final String accountType) {
-        CharSequence label = null;
-        if (mTypeToAuthDescription.containsKey(accountType)) {
-            try {
-                AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
-                Context authContext = mFragment.getActivity().createPackageContextAsUser(
-                        desc.packageName, 0 /* flags */, mUserHandle);
-                label = authContext.getResources().getText(desc.labelId);
-            } catch (PackageManager.NameNotFoundException e) {
-                LOG.w("No label name for account type " + accountType);
-            } catch (Resources.NotFoundException e) {
-                LOG.w("No label resource for account type " + accountType);
-            }
-        }
-        return label;
-    }
-
-    private static class ProviderEntry implements Comparable<ProviderEntry> {
-        private final CharSequence name;
-        private final String type;
-        ProviderEntry(CharSequence providerName, String accountType) {
-            name = providerName;
-            type = accountType;
-        }
-
-        @Override
-        public int compareTo(ProviderEntry another) {
-            if (name == null) {
-                return -1;
-            }
-            if (another.name == null) {
-                return 1;
-            }
-            return CharSequences.compareToIgnoreCase(name, another.name);
-        }
-    }
-}
diff --git a/src/com/android/car/settings/accounts/ChooseAccountPreferenceController.java b/src/com/android/car/settings/accounts/ChooseAccountPreferenceController.java
new file mode 100644
index 0000000..f28b335
--- /dev/null
+++ b/src/com/android/car/settings/accounts/ChooseAccountPreferenceController.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.collection.ArrayMap;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.accounts.AuthenticatorHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Controller for showing the user the list of accounts they can add.
+ *
+ * <p>Largely derived from {@link com.android.settings.accounts.ChooseAccountActivity}
+ */
+public class ChooseAccountPreferenceController extends
+        PreferenceController<PreferenceGroup> implements
+        AuthenticatorHelper.OnAccountsUpdateListener {
+    private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
+
+    private final UserHandle mUserHandle;
+    private AuthenticatorHelper mAuthenticatorHelper;
+    private List<String> mAuthorities;
+    private Set<String> mAccountTypesFilter;
+    private Set<String> mAccountTypesExclusionFilter;
+    private ArrayMap<String, AuthenticatorDescriptionPreference> mPreferences = new ArrayMap<>();
+
+    public ChooseAccountPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mUserHandle = new CarUserManagerHelper(context).getCurrentProcessUserInfo().getUserHandle();
+
+        HashSet<String> accountTypeExclusionFilter = new HashSet<>();
+
+        // Hardcoding bluetooth account type
+        accountTypeExclusionFilter.add("com.android.bluetooth.pbapsink");
+        setAccountTypesExclusionFilter(accountTypeExclusionFilter);
+    }
+
+    /** Sets the authorities that the user has. */
+    public void setAuthorities(List<String> authorities) {
+        mAuthorities = authorities;
+    }
+
+    /** Sets the filter for accounts that should be shown. */
+    public void setAccountTypesFilter(Set<String> accountTypesFilter) {
+        mAccountTypesFilter = accountTypesFilter;
+    }
+
+    /** Sets the filter for accounts that should NOT be shown. */
+    protected void setAccountTypesExclusionFilter(Set<String> accountTypesExclusionFilterFilter) {
+        mAccountTypesExclusionFilter = accountTypesExclusionFilterFilter;
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        forceUpdateAccountsCategory();
+    }
+
+    /** Initializes the authenticator helper. */
+    @Override
+    protected void onCreateInternal() {
+        mAuthenticatorHelper = new AuthenticatorHelper(getContext(), mUserHandle, this);
+    }
+
+    /**
+     * Registers the account update callback.
+     */
+    @Override
+    protected void onStartInternal() {
+        mAuthenticatorHelper.listenToAccountUpdates();
+    }
+
+    /**
+     * Unregisters the account update callback.
+     */
+    @Override
+    protected void onStopInternal() {
+        mAuthenticatorHelper.stopListeningToAccountUpdates();
+    }
+
+    @Override
+    public void onAccountsUpdate(UserHandle userHandle) {
+        // Only force a refresh if accounts have changed for the current user.
+        if (userHandle.equals(mUserHandle)) {
+            forceUpdateAccountsCategory();
+        }
+    }
+
+    /** Forces a refresh of the authenticator description preferences. */
+    private void forceUpdateAccountsCategory() {
+        Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
+        List<AuthenticatorDescriptionPreference> preferences =
+                getAuthenticatorDescriptionPreferences(preferencesToRemove);
+        // Add all preferences that aren't already shown
+        for (int i = 0; i < preferences.size(); i++) {
+            AuthenticatorDescriptionPreference preference = preferences.get(i);
+            preference.setOrder(i);
+            String key = preference.getKey();
+            getPreference().addPreference(preference);
+            mPreferences.put(key, preference);
+        }
+
+        // Remove all preferences that should no longer be shown
+        for (String key : preferencesToRemove) {
+            getPreference().removePreference(mPreferences.get(key));
+            mPreferences.remove(key);
+        }
+    }
+
+    /**
+     * Returns a list of preferences corresponding to the account types the user can add.
+     *
+     * <p> Derived from
+     * {@link com.android.settings.accounts.ChooseAccountActivity#onAuthDescriptionsUpdated}
+     *
+     * @param preferencesToRemove the current preferences shown; will contain the preferences that
+     *                            need to be removed from the screen after method execution
+     */
+    private List<AuthenticatorDescriptionPreference> getAuthenticatorDescriptionPreferences(
+            Set<String> preferencesToRemove) {
+        AuthenticatorDescription[] authenticatorDescriptions = AccountManager.get(
+                getContext()).getAuthenticatorTypesAsUser(
+                mUserHandle.getIdentifier());
+
+        ArrayList<AuthenticatorDescriptionPreference> authenticatorDescriptionPreferences =
+                new ArrayList<>();
+        // Create list of account providers to show on page.
+        for (AuthenticatorDescription authenticatorDescription : authenticatorDescriptions) {
+            String accountType = authenticatorDescription.type;
+            CharSequence label = mAuthenticatorHelper.getLabelForType(getContext(), accountType);
+            Drawable icon = mAuthenticatorHelper.getDrawableForType(getContext(), accountType);
+
+            List<String> accountAuthorities =
+                    mAuthenticatorHelper.getAuthoritiesForAccountType(accountType);
+
+            // If there are specific authorities required, we need to check whether they are
+            // included in the account type.
+            boolean authorized =
+                    (mAuthorities == null || mAuthorities.isEmpty() || accountAuthorities == null
+                            || !Collections.disjoint(accountAuthorities, mAuthorities));
+
+            // If there is an account type filter, make sure this account type is included.
+            authorized = authorized && (mAccountTypesFilter == null
+                    || mAccountTypesFilter.contains(accountType));
+
+            // Check if the account type is in the exclusion list.
+            authorized = authorized && (mAccountTypesExclusionFilter == null
+                    || !mAccountTypesExclusionFilter.contains(accountType));
+
+            // If authorized, add a preference for the provider to the list and remove it from
+            // preferencesToRemove.
+            if (authorized) {
+                AuthenticatorDescriptionPreference preference = mPreferences.getOrDefault(
+                        accountType,
+                        new AuthenticatorDescriptionPreference(getContext(), accountType, label,
+                                icon));
+                preference.setOnPreferenceClickListener(
+                        pref -> onAddAccount(preference.getAccountType()));
+                authenticatorDescriptionPreferences.add(preference);
+                preferencesToRemove.remove(accountType);
+            }
+        }
+
+        Collections.sort(authenticatorDescriptionPreferences, Comparator.comparing(
+                (AuthenticatorDescriptionPreference a) -> a.getTitle().toString()));
+
+        return authenticatorDescriptionPreferences;
+    }
+
+    /** Starts the {@link AddAccountActivity} to add an account for the given account type. */
+    private boolean onAddAccount(String accountType) {
+        Intent intent = new Intent(getContext(), AddAccountActivity.class);
+        intent.putExtra(AddAccountActivity.EXTRA_SELECTED_ACCOUNT, accountType);
+        getFragmentController().startActivityForResult(intent, ADD_ACCOUNT_REQUEST_CODE,
+                this::onAccountAdded);
+        return true;
+    }
+
+    private void onAccountAdded(int requestCode, int resultCode, @Nullable Intent data) {
+        if (requestCode == ADD_ACCOUNT_REQUEST_CODE) {
+            getFragmentController().goBack();
+        }
+    }
+
+    /** Used for testing to trigger an account update. */
+    @VisibleForTesting
+    AuthenticatorHelper getAuthenticatorHelper() {
+        return mAuthenticatorHelper;
+    }
+
+    /** Handles adding accounts. */
+    interface AddAccountListener {
+        /** Handles adding an account. */
+        void addAccount(String accountType);
+    }
+
+    private static class AuthenticatorDescriptionPreference extends Preference {
+        private final String mType;
+
+        AuthenticatorDescriptionPreference(Context context, String accountType, CharSequence label,
+                Drawable icon) {
+            super(context);
+            mType = accountType;
+
+            setKey(accountType);
+            setTitle(label);
+            setIcon(icon);
+        }
+
+        private String getAccountType() {
+            return mType;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/accounts/SyncPreference.java b/src/com/android/car/settings/accounts/SyncPreference.java
new file mode 100644
index 0000000..76e3f2a
--- /dev/null
+++ b/src/com/android/car/settings/accounts/SyncPreference.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.apps.common.util.Themes;
+import com.android.car.settings.R;
+
+/**
+ * A preference that represents the state of a sync adapter.
+ *
+ * <p>Largely derived from {@link com.android.settings.accounts.SyncStateSwitchPreference}.
+ */
+public class SyncPreference extends SwitchPreference {
+    private int mUid;
+    private String mPackageName;
+    private AccountSyncHelper.SyncState mSyncState = AccountSyncHelper.SyncState.NONE;
+    private boolean mOneTimeSyncMode = false;
+
+    public SyncPreference(Context context, String authority) {
+        super(context);
+        setKey(authority);
+        setPersistent(false);
+        updateIcon();
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+
+        View switchView = view.findViewById(com.android.internal.R.id.switch_widget);
+        if (mOneTimeSyncMode) {
+            switchView.setVisibility(View.GONE);
+
+            /*
+             * Override the summary. Fill in the %1$s with the existing summary
+             * (what ends up happening is the old summary is shown on the next
+             * line).
+             */
+            TextView summary = (TextView) view.findViewById(android.R.id.summary);
+            summary.setText(getContext().getString(R.string.sync_one_time_sync, getSummary()));
+        } else {
+            switchView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    /** Updates the preference icon based on the current syncing state. */
+    private void updateIcon() {
+        switch (mSyncState) {
+            case ACTIVE:
+                setIcon(R.drawable.ic_sync_anim);
+                getIcon().setTint(Themes.getAttrColor(getContext(), R.attr.iconColor));
+                break;
+            case PENDING:
+                setIcon(R.drawable.ic_sync);
+                getIcon().setTint(Themes.getAttrColor(getContext(), R.attr.iconColor));
+                break;
+            case FAILED:
+                setIcon(R.drawable.ic_sync_problem);
+                getIcon().setTint(Themes.getAttrColor(getContext(), R.attr.iconColor));
+                break;
+            default:
+                setIcon(null);
+                setIconSpaceReserved(true);
+                break;
+        }
+    }
+
+    /** Sets the sync state for this preference. */
+    public void setSyncState(AccountSyncHelper.SyncState state) {
+        mSyncState = state;
+        // Force a manual update of the icon since the sync state affects what is shown.
+        updateIcon();
+    }
+
+    /**
+     * Returns whether or not the sync adapter is in one-time sync mode.
+     *
+     * <p>One-time sync mode means that the sync adapter is not being automatically synced but
+     * can be manually synced (i.e. a one time sync).
+     */
+    public boolean isOneTimeSyncMode() {
+        return mOneTimeSyncMode;
+    }
+
+    /** Sets whether one-time sync mode is on for this preference. */
+    public void setOneTimeSyncMode(boolean oneTimeSyncMode) {
+        mOneTimeSyncMode = oneTimeSyncMode;
+        // Force a refresh so that onBindViewHolder is called
+        notifyChanged();
+    }
+
+    /**
+     * Returns the uid corresponding to the sync adapter's package.
+     *
+     * <p>This can be used to create an intent to request account access via
+     * {@link android.accounts.AccountManager#createRequestAccountAccessIntentSenderAsUser}.
+     */
+    public int getUid() {
+        return mUid;
+    }
+
+    /** Sets the uid for this preference. */
+    public void setUid(int uid) {
+        mUid = uid;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public void setPackageName(String packageName) {
+        mPackageName = packageName;
+    }
+}
diff --git a/src/com/android/car/settings/applications/AppPermissionsEntryPreferenceController.java b/src/com/android/car/settings/applications/AppPermissionsEntryPreferenceController.java
new file mode 100644
index 0000000..9743875
--- /dev/null
+++ b/src/com/android/car/settings/applications/AppPermissionsEntryPreferenceController.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.icu.text.ListFormatter;
+import android.util.ArraySet;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Updates the summary of the entry preference for app permissions to show up to a fixed number of
+ * permission groups currently permitted.
+ */
+public class AppPermissionsEntryPreferenceController extends PreferenceController<Preference> {
+
+    private static final Logger LOG = new Logger(AppPermissionsEntryPreferenceController.class);
+
+    private static final String[] PERMISSION_GROUPS = new String[]{
+            "android.permission-group.LOCATION",
+            "android.permission-group.MICROPHONE",
+            "android.permission-group.CAMERA",
+            "android.permission-group.SMS",
+            "android.permission-group.CONTACTS",
+            "android.permission-group.PHONE"};
+
+    private static final int NUM_PERMISSION_TO_USE = 3;
+
+    private PackageManager mPackageManager;
+
+    public AppPermissionsEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        Set<String> permissions = getAllPermissionsInGroups();
+        Set<String> grantedPermissionGroups = getGrantedPermissionGroups(permissions);
+        int count = 0;
+        final List<String> summaries = new ArrayList<>();
+
+        // Iterate over array instead of set to show sensitive permissions first.
+        for (String group : PERMISSION_GROUPS) {
+            if (!grantedPermissionGroups.contains(group)) {
+                continue;
+            }
+            summaries.add(getPermissionGroupLabel(group).toString().toLowerCase());
+            if (++count >= NUM_PERMISSION_TO_USE) {
+                break;
+            }
+        }
+        String summary = (count > 0) ? getContext().getString(R.string.app_permissions_summary,
+                ListFormatter.getInstance().format(summaries)) : null;
+
+        preference.setSummary(summary);
+    }
+
+    private Set<String> getAllPermissionsInGroups() {
+        Set<String> result = new ArraySet<>();
+        for (String group : PERMISSION_GROUPS) {
+            try {
+                List<PermissionInfo> permissions = mPackageManager.queryPermissionsByGroup(
+                        group, /* flags= */ 0);
+                for (PermissionInfo permissionInfo : permissions) {
+                    result.add(permissionInfo.name);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                LOG.e("Error getting permissions in group " + group, e);
+            }
+        }
+        return result;
+    }
+
+    private Set<String> getGrantedPermissionGroups(Set<String> permissions) {
+        Set<String> grantedPermissionGroups = new ArraySet<>();
+        List<PackageInfo> installedPackages =
+                mPackageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
+        for (PackageInfo installedPackage : installedPackages) {
+            if (installedPackage.permissions == null) {
+                continue;
+            }
+            for (PermissionInfo permissionInfo : installedPackage.permissions) {
+                if (permissions.contains(permissionInfo.name)) {
+                    grantedPermissionGroups.add(permissionInfo.group);
+                }
+            }
+        }
+        return grantedPermissionGroups;
+    }
+
+    private CharSequence getPermissionGroupLabel(String group) {
+        try {
+            PermissionGroupInfo groupInfo = mPackageManager.getPermissionGroupInfo(
+                    group, /* flags= */ 0);
+            return groupInfo.loadLabel(mPackageManager);
+        } catch (PackageManager.NameNotFoundException e) {
+            LOG.e("Error getting permissions label.", e);
+        }
+        return group;
+    }
+}
diff --git a/src/com/android/car/settings/applications/ApplicationDetailFragment.java b/src/com/android/car/settings/applications/ApplicationDetailFragment.java
deleted file mode 100644
index efcd64d..0000000
--- a/src/com/android/car/settings/applications/ApplicationDetailFragment.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.car.settings.applications;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.car.settings.common.Logger;
-import com.android.settingslib.Utils;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows details about an application and action associated with that application,
- * like uninstall, forceStop.
- */
-public class ApplicationDetailFragment extends ListItemSettingsFragment {
-    private static final Logger LOG = new Logger(ApplicationDetailFragment.class);
-    public static final String EXTRA_RESOLVE_INFO = "extra_resolve_info";
-
-    private ResolveInfo mResolveInfo;
-    private PackageInfo mPackageInfo;
-
-    private Button mDisableToggle;
-    private Button mForceStopButton;
-    private DevicePolicyManager mDpm;
-
-    public static ApplicationDetailFragment getInstance(ResolveInfo resolveInfo) {
-        ApplicationDetailFragment applicationDetailFragment = new ApplicationDetailFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putParcelable(EXTRA_RESOLVE_INFO, resolveInfo);
-        bundle.putInt(EXTRA_TITLE_ID, R.string.applications_settings);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        applicationDetailFragment.setArguments(bundle);
-        return applicationDetailFragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mResolveInfo = getArguments().getParcelable(EXTRA_RESOLVE_INFO);
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        mPackageInfo = getPackageInfo();
-        super.onActivityCreated(savedInstanceState);
-        if (mResolveInfo == null) {
-            LOG.w("No application info set.");
-            return;
-        }
-
-        mDisableToggle = (Button) getActivity().findViewById(R.id.action_button1);
-        mForceStopButton = (Button) getActivity().findViewById(R.id.action_button2);
-        mForceStopButton.setText(R.string.force_stop);
-        mForceStopButton.setVisibility(View.VISIBLE);
-
-        mDpm = (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
-        updateForceStopButton();
-        mForceStopButton.setOnClickListener(
-                v -> forceStopPackage(mResolveInfo.activityInfo.packageName));
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        updateForceStopButton();
-        updateDisableable();
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        return new ListItemProvider.ListProvider(getListItems());
-    }
-
-    private List<ListItem> getListItems() {
-        ArrayList<ListItem> items = new ArrayList<>();
-        items.add(new ApplicationLineItem(
-                getContext(),
-                getContext().getPackageManager(),
-                mResolveInfo,
-                /* fragmentController= */ null,
-                false));
-        items.add(new ApplicationPermissionLineItem(getContext(), mResolveInfo, this));
-        TextListItem versionItem = new TextListItem(getContext());
-        versionItem.setTitle(getContext().getString(
-                R.string.application_version_label, mPackageInfo.versionName));
-        items.add(versionItem);
-        return items;
-    }
-
-    // fetch the latest ApplicationInfo instead of caching it so it reflects the current state.
-    private ApplicationInfo getAppInfo() {
-        try {
-            return getContext().getPackageManager().getApplicationInfo(
-                    mResolveInfo.activityInfo.packageName, 0 /* flag */);
-        } catch (PackageManager.NameNotFoundException e) {
-            LOG.e("incorrect packagename: " + mResolveInfo.activityInfo.packageName, e);
-            throw new IllegalArgumentException(e);
-        }
-    }
-
-    private PackageInfo getPackageInfo() {
-        try {
-            return getContext().getPackageManager().getPackageInfo(
-                    mResolveInfo.activityInfo.packageName, 0 /* flag */);
-        } catch (PackageManager.NameNotFoundException e) {
-            LOG.e("incorrect packagename: " + mResolveInfo.activityInfo.packageName, e);
-            throw new IllegalArgumentException(e);
-        }
-    }
-
-    private void updateDisableable() {
-        boolean disableable = false;
-        boolean disabled = false;
-        // Try to prevent the user from bricking their phone
-        // by not allowing disabling of apps in the system.
-        if (Utils.isSystemPackage(
-                getResources(), getContext().getPackageManager(), mPackageInfo)) {
-            // Disable button for core system applications.
-            mDisableToggle.setText(R.string.disable_text);
-            disabled = false;
-        } else if (getAppInfo().enabled && !isDisabledUntilUsed()) {
-            mDisableToggle.setText(R.string.disable_text);
-            disableable = true;
-            disabled = false;
-        } else {
-            mDisableToggle.setText(R.string.enable_text);
-            disableable = true;
-            disabled = true;
-        }
-        mDisableToggle.setEnabled(disableable);
-        final int enableState = disabled
-                ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
-                : PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
-        mDisableToggle.setOnClickListener(v -> {
-            getContext().getPackageManager().setApplicationEnabledSetting(
-                    mResolveInfo.activityInfo.packageName,
-                    enableState,
-                    0);
-            updateDisableable();
-        });
-    }
-
-    private boolean isDisabledUntilUsed() {
-        return getAppInfo().enabledSetting
-                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
-    }
-
-    private void forceStopPackage(String pkgName) {
-        ActivityManager am = (ActivityManager) getContext().getSystemService(
-                Context.ACTIVITY_SERVICE);
-        LOG.d("Stopping package " + pkgName);
-        am.forceStopPackage(pkgName);
-        updateForceStopButton();
-    }
-
-    // enable or disable the force stop button:
-    // - disabled if it's a device admin
-    // - if the application is stopped unexplicitly, enabled the button
-    // - if there's a reason for the system to restart the application, that indicates the app
-    //   can be force stopped.
-    private void updateForceStopButton() {
-        if (mDpm.packageHasActiveAdmins(mResolveInfo.activityInfo.packageName)) {
-            // User can't force stop device admin.
-            LOG.d("Disabling button, user can't force stop device admin");
-            mForceStopButton.setEnabled(false);
-        } else if ((getAppInfo().flags & ApplicationInfo.FLAG_STOPPED) == 0) {
-            // If the app isn't explicitly stopped, then always show the
-            // force stop button.
-            LOG.w("App is not explicitly stopped");
-            mForceStopButton.setEnabled(true);
-        } else {
-            Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
-                    Uri.fromParts("package", mResolveInfo.activityInfo.packageName, null));
-            intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{
-                    mResolveInfo.activityInfo.packageName
-            });
-            LOG.d("Sending broadcast to query restart for "
-                    + mResolveInfo.activityInfo.packageName);
-            getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
-                    mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
-        }
-    }
-
-    private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
-            LOG.d(MessageFormat.format("Got broadcast response: Restart status for {0} {1}",
-                    mResolveInfo.activityInfo.packageName, enabled));
-            mForceStopButton.setEnabled(enabled);
-        }
-    };
-}
diff --git a/src/com/android/car/settings/applications/ApplicationDetailsFragment.java b/src/com/android/car/settings/applications/ApplicationDetailsFragment.java
new file mode 100644
index 0000000..275968c
--- /dev/null
+++ b/src/com/android/car/settings/applications/ApplicationDetailsFragment.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import static android.app.Activity.RESULT_OK;
+
+import static com.android.car.settings.applications.ApplicationsUtils.isFallbackPackage;
+import static com.android.car.settings.applications.ApplicationsUtils.isKeepEnabledPackage;
+import static com.android.car.settings.applications.ApplicationsUtils.isProfileOrDeviceOwner;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ActivityResultCallback;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.Utils;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Shows details about an application and action associated with that application, like uninstall,
+ * forceStop.
+ *
+ * <p>To uninstall an app, it must <i>not</i> be:
+ * <ul>
+ * <li>a system bundled app
+ * <li>system signed
+ * <li>managed by an active admin from a device policy
+ * <li>a device or profile owner
+ * <li>the only home app
+ * <li>the default home app
+ * <li>for a user with the {@link UserManager#DISALLOW_APPS_CONTROL} restriction
+ * <li>for a user with the {@link UserManager#DISALLOW_UNINSTALL_APPS} restriction
+ * </ul>
+ *
+ * <p>For apps that cannot be uninstalled, a disable option is shown instead (or enable if the app
+ * is already disabled).
+ */
+public class ApplicationDetailsFragment extends SettingsFragment implements ActivityResultCallback {
+    private static final Logger LOG = new Logger(ApplicationDetailsFragment.class);
+    public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
+
+    @VisibleForTesting
+    static final String DISABLE_CONFIRM_DIALOG_TAG =
+            "com.android.car.settings.applications.DisableConfirmDialog";
+    @VisibleForTesting
+    static final String FORCE_STOP_CONFIRM_DIALOG_TAG =
+            "com.android.car.settings.applications.ForceStopConfirmDialog";
+    @VisibleForTesting
+    static final int UNINSTALL_REQUEST_CODE = 10;
+
+    private DevicePolicyManager mDpm;
+    private PackageManager mPm;
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    private String mPackageName;
+    private PackageInfo mPackageInfo;
+    private ApplicationsState mAppState;
+    private ApplicationsState.Session mSession;
+    private ApplicationsState.AppEntry mAppEntry;
+
+    // The function of this button depends on which app is shown and the app's current state.
+    // It is an application enable/disable toggle for apps bundled with the system image.
+    private Button mUninstallButton;
+    private Button mForceStopButton;
+
+    /** Creates an instance of this fragment, passing packageName as an argument. */
+    public static ApplicationDetailsFragment getInstance(String packageName) {
+        ApplicationDetailsFragment applicationDetailFragment = new ApplicationDetailsFragment();
+        Bundle bundle = new Bundle();
+        bundle.putString(EXTRA_PACKAGE_NAME, packageName);
+        applicationDetailFragment.setArguments(bundle);
+        return applicationDetailFragment;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.application_details_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mPm = context.getPackageManager();
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+
+        // These should be loaded before onCreate() so that the controller operates as expected.
+        mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME);
+
+        mAppState = ApplicationsState.getInstance(requireActivity().getApplication());
+        mSession = mAppState.newSession(mApplicationStateCallbacks, getLifecycle());
+
+        retrieveAppEntry();
+
+        use(ApplicationPreferenceController.class,
+                R.string.pk_application_details_app)
+                .setAppEntry(mAppEntry).setAppState(mAppState);
+        use(NotificationsPreferenceController.class,
+                R.string.pk_application_details_notifications).setPackageInfo(mPackageInfo);
+        use(PermissionsPreferenceController.class,
+                R.string.pk_application_details_permissions).setPackageName(mPackageName);
+        use(VersionPreferenceController.class,
+                R.string.pk_application_details_version).setPackageInfo(mPackageInfo);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ConfirmationDialogFragment.resetListeners(
+                (ConfirmationDialogFragment) findDialogByTag(DISABLE_CONFIRM_DIALOG_TAG),
+                mDisableConfirmListener, /* rejectListener= */ null);
+        ConfirmationDialogFragment.resetListeners(
+                (ConfirmationDialogFragment) findDialogByTag(FORCE_STOP_CONFIRM_DIALOG_TAG),
+                mForceStopConfirmListener, /* rejectListener= */ null);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        mUninstallButton = requireActivity().findViewById(R.id.action_button1);
+        mForceStopButton = requireActivity().findViewById(R.id.action_button2);
+        mForceStopButton.setVisibility(View.VISIBLE);
+        mForceStopButton.setEnabled(false);
+        mForceStopButton.setText(R.string.force_stop);
+        mForceStopButton.setOnClickListener(mForceStopClickListener);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        // Resume the session earlier than the lifecycle so that cached information is updated
+        // even if settings is not resumed (for example in multi-display).
+        mSession.onResume();
+        refresh();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        // Since we resume early in onStart, make sure we clean up even if we don't receive onPause.
+        mSession.onPause();
+    }
+
+    private void refresh() {
+        retrieveAppEntry();
+        if (mAppEntry == null) {
+            goBack();
+        }
+        updateForceStopButton();
+        updateUninstallButton();
+    }
+
+    private void retrieveAppEntry() {
+        mAppEntry = mAppState.getEntry(mPackageName,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+        if (mAppEntry != null) {
+            try {
+                mPackageInfo = mPm.getPackageInfo(mPackageName,
+                        PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_ANY_USER
+                                | PackageManager.GET_SIGNATURES | PackageManager.GET_PERMISSIONS);
+            } catch (PackageManager.NameNotFoundException e) {
+                LOG.e("Exception when retrieving package:" + mPackageName, e);
+                mPackageInfo = null;
+            }
+        } else {
+            mPackageInfo = null;
+        }
+    }
+
+    private void updateForceStopButton() {
+        if (mDpm.packageHasActiveAdmins(mPackageName)) {
+            updateForceStopButtonInner(/* enabled= */ false);
+        } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
+            // If the app isn't explicitly stopped, then always show the force stop button.
+            updateForceStopButtonInner(/* enabled= */ true);
+        } else {
+            Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
+                    Uri.fromParts("package", mPackageName, /* fragment= */ null));
+            intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{mPackageName});
+            intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid));
+            LOG.d("Sending broadcast to query restart status for " + mPackageName);
+            requireContext().sendOrderedBroadcastAsUser(intent,
+                    UserHandle.CURRENT,
+                    /* receiverPermission= */ null,
+                    mCheckKillProcessesReceiver,
+                    /* scheduler= */ null,
+                    Activity.RESULT_CANCELED,
+                    /* initialData= */ null,
+                    /* initialExtras= */ null);
+        }
+    }
+
+    private void updateForceStopButtonInner(boolean enabled) {
+        mForceStopButton.setEnabled(
+                enabled && !mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                        UserManager.DISALLOW_APPS_CONTROL));
+    }
+
+    private void updateUninstallButton() {
+        if (isBundledApp()) {
+            if (isAppEnabled()) {
+                mUninstallButton.setText(R.string.disable_text);
+                mUninstallButton.setOnClickListener(mDisableClickListener);
+            } else {
+                mUninstallButton.setText(R.string.enable_text);
+                mUninstallButton.setOnClickListener(mEnableClickListener);
+            }
+        } else {
+            mUninstallButton.setText(R.string.uninstall_text);
+            mUninstallButton.setOnClickListener(mUninstallClickListener);
+        }
+
+        mUninstallButton.setEnabled(!shouldDisableUninstallButton());
+    }
+
+    private boolean shouldDisableUninstallButton() {
+        // TODO: Remove this condition when phone settings is removed from automotive builds.
+        // This is a temporary measure to allow enabling the phone settings package.
+        // It is not an expected case long-term to have packages in the disabled state that
+        // normally cannot be disabled by a user.
+        if (mPackageName.equals("com.android.settings")) {
+            return false;
+        }
+
+        if (shouldDisableUninstallForHomeApp()) {
+            LOG.d("Uninstall disabled for home app");
+            return true;
+        }
+
+        if (isAppEnabled() && isKeepEnabledPackage(requireContext(), mPackageName)) {
+            LOG.d("Disable button disabled for keep enabled package");
+            return true;
+        }
+
+        if (Utils.isSystemPackage(getResources(), mPm, mPackageInfo)) {
+            LOG.d("Uninstall disabled for system package");
+            return true;
+        }
+
+        if (mDpm.packageHasActiveAdmins(mPackageName)) {
+            LOG.d("Uninstall disabled because package has active admins");
+            return true;
+        }
+
+        // We don't allow uninstalling profile/device owner on any user because if it's a system
+        // app, "uninstall" is actually "downgrade to the system version + disable", and
+        // "downgrade" will clear data on all users.
+        if (isProfileOrDeviceOwner(mPackageName, mDpm, mCarUserManagerHelper)) {
+            LOG.d("Uninstall disabled because package is profile or device owner");
+            return true;
+        }
+
+        if (mDpm.isUninstallInQueue(mPackageName)) {
+            LOG.d("Uninstall disabled because intent is already queued");
+            return true;
+        }
+
+        if (mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_APPS_CONTROL)) {
+            LOG.d("Uninstall disabled because user has DISALLOW_APPS_CONTROL restriction");
+            return true;
+        }
+
+        if (mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_UNINSTALL_APPS)) {
+            LOG.d("Uninstall disabled because user has DISALLOW_UNINSTALL_APPS restriction");
+            return true;
+        }
+
+        if (isFallbackPackage(mPackageName)) {
+            LOG.d("Uninstall disabled because package is a fallback package");
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if the package is a Home app that should not be uninstalled. We don't
+     * risk downgrading bundled home apps because that can interfere with home-key resolution. We
+     * can't allow removal of the only home app, and we don't want to allow removal of an
+     * explicitly preferred home app. The user can go to Home settings and pick a different app,
+     * after which we'll permit removal of the now-not-default app.
+     */
+    private boolean shouldDisableUninstallForHomeApp() {
+        Set<String> homePackages = new ArraySet<>();
+        // Get list of "home" apps and trace through any meta-data references.
+        List<ResolveInfo> homeActivities = new ArrayList<>();
+        ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
+        for (int i = 0; i < homeActivities.size(); i++) {
+            ResolveInfo ri = homeActivities.get(i);
+            String activityPkg = ri.activityInfo.packageName;
+            homePackages.add(activityPkg);
+
+            // Also make sure to include anything proxying for the home app.
+            Bundle metadata = ri.activityInfo.metaData;
+            if (metadata != null) {
+                String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
+                if (signaturesMatch(metaPkg, activityPkg)) {
+                    homePackages.add(metaPkg);
+                }
+            }
+        }
+
+        if (homePackages.contains(mPackageName)) {
+            boolean isBundledApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            if (isBundledApp) {
+                // Don't risk a downgrade.
+                return true;
+            } else if (currentDefaultHome == null) {
+                // No preferred default. Permit uninstall only when there is more than one
+                // candidate.
+                return (homePackages.size() == 1);
+            } else {
+                // Explicit default home app. Forbid uninstall of that one, but permit it for
+                // installed-but-inactive ones.
+                return mPackageName.equals(currentDefaultHome.getPackageName());
+            }
+        } else {
+            // Not a home app.
+            return false;
+        }
+    }
+
+    private boolean signaturesMatch(String pkg1, String pkg2) {
+        if (pkg1 != null && pkg2 != null) {
+            try {
+                int match = mPm.checkSignatures(pkg1, pkg2);
+                if (match >= PackageManager.SIGNATURE_MATCH) {
+                    return true;
+                }
+            } catch (Exception e) {
+                // e.g. package not found during lookup. Possibly bad input.
+                // Just return false as this isn't a reason to crash given the use case.
+            }
+        }
+        return false;
+    }
+
+    private boolean isBundledApp() {
+        return (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    private boolean isAppEnabled() {
+        return mAppEntry.info.enabled && !(mAppEntry.info.enabledSetting
+                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+    }
+
+    @Override
+    public void processActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        if (requestCode == UNINSTALL_REQUEST_CODE) {
+            if (resultCode == RESULT_OK) {
+                goBack();
+            } else {
+                LOG.e("Uninstall failed with result " + resultCode);
+            }
+        }
+    }
+
+    private final View.OnClickListener mForceStopClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            ConfirmationDialogFragment dialogFragment =
+                    new ConfirmationDialogFragment.Builder(getContext())
+                            .setTitle(R.string.force_stop_dialog_title)
+                            .setMessage(R.string.force_stop_dialog_text)
+                            .setPositiveButton(android.R.string.ok,
+                                    mForceStopConfirmListener)
+                            .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null)
+                            .build();
+            showDialog(dialogFragment, FORCE_STOP_CONFIRM_DIALOG_TAG);
+        }
+    };
+
+    private final ConfirmationDialogFragment.ConfirmListener mForceStopConfirmListener =
+            new ConfirmationDialogFragment.ConfirmListener() {
+                @Override
+                public void onConfirm(@Nullable Bundle arguments) {
+                    ActivityManager am = (ActivityManager) requireContext().getSystemService(
+                            Context.ACTIVITY_SERVICE);
+                    LOG.d("Stopping package " + mPackageName);
+                    am.forceStopPackage(mPackageName);
+                    int userId = UserHandle.getUserId(mAppEntry.info.uid);
+                    mAppState.invalidatePackage(mPackageName, userId);
+                }
+            };
+
+    private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
+            LOG.d("Got broadcast response: Restart status for " + mPackageName + " " + enabled);
+            updateForceStopButtonInner(enabled);
+        }
+    };
+
+    private final View.OnClickListener mDisableClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            ConfirmationDialogFragment dialogFragment =
+                    new ConfirmationDialogFragment.Builder(getContext())
+                            .setMessage(getString(R.string.app_disable_dialog_text))
+                            .setPositiveButton(R.string.app_disable_dialog_positive,
+                                    mDisableConfirmListener)
+                            .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null)
+                            .build();
+            showDialog(dialogFragment, DISABLE_CONFIRM_DIALOG_TAG);
+        }
+    };
+
+    private final ConfirmationDialogFragment.ConfirmListener mDisableConfirmListener =
+            new ConfirmationDialogFragment.ConfirmListener() {
+                @Override
+                public void onConfirm(@Nullable Bundle arguments) {
+                    mPm.setApplicationEnabledSetting(mPackageName,
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, /* flags= */ 0);
+                }
+            };
+
+    private final View.OnClickListener mEnableClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mPm.setApplicationEnabledSetting(mPackageName,
+                    PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, /* flags= */ 0);
+        }
+    };
+
+    private final View.OnClickListener mUninstallClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            Uri packageUri = Uri.parse("package:" + mPackageName);
+
+            Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
+            uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, true);
+            uninstallIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
+
+            startActivityForResult(uninstallIntent, UNINSTALL_REQUEST_CODE, /* callback= */
+                    ApplicationDetailsFragment.this);
+        }
+    };
+
+    private final ApplicationsState.Callbacks mApplicationStateCallbacks =
+            new ApplicationsState.Callbacks() {
+                @Override
+                public void onRunningStateChanged(boolean running) {
+                }
+
+                @Override
+                public void onPackageListChanged() {
+                    refresh();
+                }
+
+                @Override
+                public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+                }
+
+                @Override
+                public void onPackageIconChanged() {
+                }
+
+                @Override
+                public void onPackageSizeChanged(String packageName) {
+                }
+
+                @Override
+                public void onAllSizesComputed() {
+                }
+
+                @Override
+                public void onLauncherInfoChanged() {
+                }
+
+                @Override
+                public void onLoadEntriesCompleted() {
+                }
+            };
+}
diff --git a/src/com/android/car/settings/applications/ApplicationLineItem.java b/src/com/android/car/settings/applications/ApplicationLineItem.java
deleted file mode 100644
index cafeefc..0000000
--- a/src/com/android/car/settings/applications/ApplicationLineItem.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.applications;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-
-/**
- * Represents an application in application settings page.
- */
-public class ApplicationLineItem extends TextListItem {
-
-    public ApplicationLineItem(
-            @NonNull Context context,
-            PackageManager pm,
-            ResolveInfo resolveInfo,
-            BaseFragment.FragmentController fragmentController) {
-        this(context, pm, resolveInfo, fragmentController, true);
-    }
-
-    public ApplicationLineItem(
-            @NonNull Context context,
-            PackageManager pm,
-            ResolveInfo resolveInfo,
-            BaseFragment.FragmentController fragmentController,
-            boolean clickable) {
-        super(context);
-        setTitle(resolveInfo.loadLabel(pm).toString());
-        setPrimaryActionIcon(resolveInfo.loadIcon(pm), /* useLargeIcon= */ false);
-        if (clickable) {
-            setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-            setOnClickListener(v ->
-                    fragmentController.launchFragment(
-                            ApplicationDetailFragment.getInstance(resolveInfo)));
-        }
-    }
-}
diff --git a/src/com/android/car/settings/applications/ApplicationListItemManager.java b/src/com/android/car/settings/applications/ApplicationListItemManager.java
new file mode 100644
index 0000000..7293539
--- /dev/null
+++ b/src/com/android/car/settings/applications/ApplicationListItemManager.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.applications;
+
+import android.graphics.drawable.Drawable;
+import android.os.storage.VolumeInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Class used to load the applications installed on the system with their metadata.
+ */
+public class ApplicationListItemManager implements ApplicationsState.Callbacks {
+    /**
+     * Callback that is called once the list of applications are loaded.
+     */
+    public interface AppListItemListener {
+        /**
+         * Called when the data is successfully loaded from {@link ApplicationsState.Callbacks} and
+         * icon, title and summary are set for all the applications.
+         */
+        void onDataLoaded(ArrayList<ApplicationsState.AppEntry> apps);
+    }
+
+    private final VolumeInfo mVolumeInfo;
+    private final Lifecycle mLifecycle;
+    private final ApplicationsState mAppState;
+    private final List<AppListItemListener> mAppListItemListeners = new ArrayList<>();
+
+    private ApplicationsState.Session mSession;
+    private ApplicationsState.AppFilter mAppFilter;
+    private Comparator<ApplicationsState.AppEntry> mAppEntryComparator;
+
+    public ApplicationListItemManager(VolumeInfo volumeInfo, Lifecycle lifecycle,
+            ApplicationsState appState) {
+        mVolumeInfo = volumeInfo;
+        mLifecycle = lifecycle;
+        mAppState = appState;
+    }
+
+    /**
+     * Registers a listener that will be notified once the data is loaded.
+     */
+    public void registerListener(AppListItemListener appListItemListener) {
+        if (!mAppListItemListeners.contains(appListItemListener) && appListItemListener != null) {
+            mAppListItemListeners.add(appListItemListener);
+        }
+    }
+
+    /**
+     * Unregisters the listener.
+     */
+    public void unregisterlistener(AppListItemListener appListItemListener) {
+        mAppListItemListeners.remove(appListItemListener);
+    }
+
+    /**
+     * Resumes the session on fragment start.
+     */
+    public void onFragmentStart() {
+        mSession.onResume();
+    }
+
+    /**
+     * Pause the session on fragment stop.
+     */
+    public void onFragmentStop() {
+        mSession.onPause();
+    }
+
+    /**
+     * Starts the new session and start loading the list of installed applications on the device.
+     * This list will be filtered out based on the {@link ApplicationsState.AppFilter} provided.
+     * Once the list is ready {@link AppListItemListener#onDataLoaded} method will be called.
+     *
+     * @param appFilter based on which the list of applications will be filtered before returning.
+     * @param appEntryComparator comparator based on which the application list will be sorted.
+     */
+    public void startLoading(ApplicationsState.AppFilter appFilter,
+            Comparator<ApplicationsState.AppEntry> appEntryComparator) {
+        mAppFilter = appFilter;
+        mAppEntryComparator = appEntryComparator;
+        mSession = mAppState.newSession(this, mLifecycle);
+    }
+
+    @Override
+    public void onPackageIconChanged() {
+        rebuild();
+    }
+
+    @Override
+    public void onPackageSizeChanged(String packageName) {
+        rebuild();
+    }
+
+    @Override
+    public void onAllSizesComputed() {
+        rebuild();
+    }
+
+    @Override
+    public void onLauncherInfoChanged() {
+        rebuild();
+    }
+
+    @Override
+    public void onLoadEntriesCompleted() {
+        rebuild();
+    }
+
+    @Override
+    public void onRunningStateChanged(boolean running) {
+    }
+
+    @Override
+    public void onPackageListChanged() {
+        rebuild();
+    }
+
+    @Override
+    public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+        List<String> successfullyLoadedApplications = new ArrayList<>();
+        for (ApplicationsState.AppEntry appEntry : apps) {
+            String key = appEntry.label + appEntry.sizeStr;
+            if (isLoaded(appEntry.label,
+                    appEntry.sizeStr, appEntry.icon)) {
+                successfullyLoadedApplications.add(key);
+            }
+        }
+
+        if (successfullyLoadedApplications.size() == apps.size()) {
+            for (AppListItemListener appListItemListener : mAppListItemListeners) {
+                appListItemListener.onDataLoaded(apps);
+            }
+        }
+    }
+
+    private boolean isLoaded(String title, String summary, Drawable icon) {
+        return title != null && summary != null && icon != null;
+    }
+
+    ApplicationsState.AppFilter getCompositeFilter(String volumeUuid) {
+        if (mAppFilter == null) {
+            return null;
+        }
+        ApplicationsState.AppFilter filter = new ApplicationsState.VolumeFilter(volumeUuid);
+        filter = new ApplicationsState.CompoundFilter(mAppFilter, filter);
+        return filter;
+    }
+
+    private void rebuild() {
+        ApplicationsState.AppFilter filterObj = ApplicationsState.FILTER_EVERYTHING;
+
+        filterObj = new ApplicationsState.CompoundFilter(filterObj,
+                ApplicationsState.FILTER_NOT_HIDE);
+        ApplicationsState.AppFilter compositeFilter = getCompositeFilter(mVolumeInfo.getFsUuid());
+        if (compositeFilter != null) {
+            filterObj = new ApplicationsState.CompoundFilter(filterObj, compositeFilter);
+        }
+        filterObj = new ApplicationsState.CompoundFilter(filterObj,
+                ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT);
+        ApplicationsState.AppFilter finalFilterObj = filterObj;
+        mSession.rebuild(finalFilterObj, mAppEntryComparator, /* foreground= */ false);
+    }
+}
diff --git a/src/com/android/car/settings/applications/ApplicationPermissionLineItem.java b/src/com/android/car/settings/applications/ApplicationPermissionLineItem.java
deleted file mode 100644
index 157aaec..0000000
--- a/src/com/android/car/settings/applications/ApplicationPermissionLineItem.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.applications;
-
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.icu.text.ListFormatter;
-import android.text.TextUtils;
-
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.AnimationUtil;
-import com.android.car.settings.common.ListController;
-import com.android.car.settings.common.Logger;
-import com.android.settingslib.applications.PermissionsSummaryHelper;
-import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows details about an application and action associated with that application,
- * like uninstall, forceStop.
- */
-public class ApplicationPermissionLineItem extends TextListItem {
-    private static final Logger LOG = new Logger(ApplicationPermissionLineItem.class);
-
-    private final ResolveInfo mResolveInfo;
-    private final Context mContext;
-    private final ListController mListController;
-    private String mSummary;
-
-    public ApplicationPermissionLineItem(Context context, ResolveInfo resolveInfo,
-            ListController listController) {
-        super(context);
-        mResolveInfo = resolveInfo;
-        mContext = context;
-        mListController = listController;
-
-        PermissionsSummaryHelper.getPermissionSummary(mContext,
-                mResolveInfo.activityInfo.packageName, mPermissionCallback);
-        setTitle(context.getString(R.string.permissions_label));
-        setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-        updateBody();
-    }
-
-    private void updateBody() {
-        if (TextUtils.isEmpty(mSummary)) {
-            setBody(mContext.getString(R.string.computing_size));
-            setOnClickListener(null);
-        } else {
-            setBody(mSummary);
-            setOnClickListener(view -> {
-                // start new activity to manage app permissions
-                Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
-                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mResolveInfo.activityInfo.packageName);
-                try {
-                    mContext.startActivity(
-                            intent, AnimationUtil.slideInFromRightOption(mContext).toBundle());
-                } catch (ActivityNotFoundException e) {
-                    LOG.w("No app can handle android.intent.action.MANAGE_APP_PERMISSIONS");
-                }
-            });
-        }
-    }
-
-    private final PermissionsResultCallback mPermissionCallback = new PermissionsResultCallback() {
-        @Override
-        public void onPermissionSummaryResult(int standardGrantedPermissionCount,
-                int requestedPermissionCount, int additionalGrantedPermissionCount,
-                List<CharSequence> grantedGroupLabels) {
-            Resources res = mContext.getResources();
-
-            if (requestedPermissionCount == 0) {
-                mSummary = res.getString(
-                        R.string.runtime_permissions_summary_no_permissions_requested);
-            } else {
-                ArrayList<CharSequence> list = new ArrayList<>(grantedGroupLabels);
-                if (additionalGrantedPermissionCount > 0) {
-                    // N additional permissions.
-                    list.add(res.getQuantityString(
-                            R.plurals.runtime_permissions_additional_count,
-                            additionalGrantedPermissionCount, additionalGrantedPermissionCount));
-                }
-                if (list.isEmpty()) {
-                    mSummary = res.getString(
-                            R.string.runtime_permissions_summary_no_permissions_granted);
-                } else {
-                    mSummary = ListFormatter.getInstance().format(list);
-                }
-            }
-            updateBody();
-            mListController.refreshList();
-        }
-    };
-}
diff --git a/src/com/android/car/settings/applications/ApplicationPreferenceController.java b/src/com/android/car/settings/applications/ApplicationPreferenceController.java
new file mode 100644
index 0000000..63e0227
--- /dev/null
+++ b/src/com/android/car/settings/applications/ApplicationPreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+/** Business logic for the Application field in the application details page. */
+public class ApplicationPreferenceController extends PreferenceController<Preference> {
+
+    private AppEntry mAppEntry;
+    private ApplicationsState mApplicationsState;
+
+    public ApplicationPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /** Sets the {@link AppEntry} which is used to load the app name and icon. */
+    public ApplicationPreferenceController setAppEntry(AppEntry appEntry) {
+        mAppEntry = appEntry;
+        return this;
+    }
+
+    /** Sets the {@link ApplicationsState} which is used to load the app name and icon. */
+    public ApplicationPreferenceController setAppState(ApplicationsState applicationsState) {
+        mApplicationsState = applicationsState;
+        return this;
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mAppEntry == null || mApplicationsState == null) {
+            throw new IllegalStateException(
+                    "AppEntry and AppState should be set before calling this function");
+        }
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setTitle(getAppName());
+        preference.setIcon(getAppIcon());
+    }
+
+    protected String getAppName() {
+        mAppEntry.ensureLabel(getContext());
+        return mAppEntry.label;
+    }
+
+    protected Drawable getAppIcon() {
+        mApplicationsState.ensureIcon(mAppEntry);
+        return mAppEntry.icon;
+    }
+
+    protected String getAppVersion() {
+        return mAppEntry.getVersion(getContext());
+    }
+}
diff --git a/src/com/android/car/settings/applications/ApplicationSettingsFragment.java b/src/com/android/car/settings/applications/ApplicationSettingsFragment.java
deleted file mode 100644
index eb1aa94..0000000
--- a/src/com/android/car/settings/applications/ApplicationSettingsFragment.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.settings.applications;
-
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Lists all installed applications and their summary.
- */
-public class ApplicationSettingsFragment extends ListItemSettingsFragment {
-
-    /**
-     * Gets an instance of this object.
-     */
-    public static ApplicationSettingsFragment newInstance() {
-        ApplicationSettingsFragment applicationSettingsFragment = new ApplicationSettingsFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.applications_settings);
-        applicationSettingsFragment.setArguments(bundle);
-        return applicationSettingsFragment;
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        return new ListItemProvider.ListProvider(getListItems());
-    }
-
-    private List<ListItem> getListItems() {
-        PackageManager pm = getContext().getPackageManager();
-        Intent intent= new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(Intent.CATEGORY_LAUNCHER);
-        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent,
-                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_DISABLED_COMPONENTS);
-        ArrayList<ListItem> items = new ArrayList<>();
-        for (ResolveInfo resolveInfo : resolveInfos) {
-            items.add(new ApplicationLineItem(
-                    getContext(), pm, resolveInfo, getFragmentController()));
-        }
-        return items;
-    }
-}
diff --git a/src/com/android/car/settings/applications/ApplicationsSettingsFragment.java b/src/com/android/car/settings/applications/ApplicationsSettingsFragment.java
new file mode 100644
index 0000000..c736e5d
--- /dev/null
+++ b/src/com/android/car/settings/applications/ApplicationsSettingsFragment.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import static com.android.car.settings.storage.StorageUtils.maybeInitializeVolume;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.applications.ApplicationsState;
+
+/**
+ * Lists all installed applications and their summary.
+ */
+public class ApplicationsSettingsFragment extends SettingsFragment {
+
+    private ApplicationListItemManager mAppListItemManager;
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.applications_settings_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        Application application = requireActivity().getApplication();
+        StorageManager sm = context.getSystemService(StorageManager.class);
+        VolumeInfo volume = maybeInitializeVolume(sm, getArguments());
+        mAppListItemManager = new ApplicationListItemManager(volume, getLifecycle(),
+                ApplicationsState.getInstance(application));
+        mAppListItemManager.registerListener(
+                use(ApplicationsSettingsPreferenceController.class,
+                        R.string.pk_all_applications_settings_list));
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAppListItemManager.startLoading(/* AppFilter= */ null, ApplicationsState.ALPHA_COMPARATOR);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mAppListItemManager.onFragmentStart();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mAppListItemManager.onFragmentStop();
+    }
+}
diff --git a/src/com/android/car/settings/applications/ApplicationsSettingsPreferenceController.java b/src/com/android/car/settings/applications/ApplicationsSettingsPreferenceController.java
new file mode 100644
index 0000000..f94e9af
--- /dev/null
+++ b/src/com/android/car/settings/applications/ApplicationsSettingsPreferenceController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.ArrayList;
+
+/** Business logic which populates the applications in this setting. */
+public class ApplicationsSettingsPreferenceController extends
+        PreferenceController<PreferenceGroup> implements
+        ApplicationListItemManager.AppListItemListener {
+
+    public ApplicationsSettingsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    public void onDataLoaded(ArrayList<ApplicationsState.AppEntry> apps) {
+        getPreference().removeAll();
+        for (ApplicationsState.AppEntry appEntry : apps) {
+            getPreference().addPreference(
+                    createPreference(appEntry.label, appEntry.sizeStr, appEntry.icon,
+                            appEntry.info.packageName));
+        }
+    }
+
+    private Preference createPreference(String title, String summary, Drawable icon,
+            String packageName) {
+        Preference preference = new Preference(getContext());
+        preference.setTitle(title);
+        preference.setSummary(summary);
+        preference.setIcon(icon);
+        preference.setKey(packageName);
+        preference.setOnPreferenceClickListener(p -> {
+            getFragmentController().launchFragment(
+                    ApplicationDetailsFragment.getInstance(packageName));
+            return true;
+        });
+        return preference;
+    }
+}
diff --git a/src/com/android/car/settings/applications/ApplicationsUtils.java b/src/com/android/car/settings/applications/ApplicationsUtils.java
new file mode 100644
index 0000000..2047000
--- /dev/null
+++ b/src/com/android/car/settings/applications/ApplicationsUtils.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telecom.DefaultDialerManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.webkit.IWebViewUpdateService;
+
+import com.android.internal.telephony.SmsApplication;
+
+import java.util.List;
+import java.util.Set;
+
+/** Utility functions for use in applications settings. */
+public class ApplicationsUtils {
+
+    private ApplicationsUtils() {
+    }
+
+    /**
+     * Returns {@code true} if the package should always remain enabled.
+     */
+    // TODO: investigate making this behavior configurable via a feature factory with the current
+    //  contents as the default.
+    public static boolean isKeepEnabledPackage(Context context, String packageName) {
+        // Find current default phone/sms app. We should keep them enabled.
+        Set<String> keepEnabledPackages = new ArraySet<>();
+        String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(context);
+        if (!TextUtils.isEmpty(defaultDialer)) {
+            keepEnabledPackages.add(defaultDialer);
+        }
+        ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(
+                context, /* updateIfNeeded= */ true);
+        if (defaultSms != null) {
+            keepEnabledPackages.add(defaultSms.getPackageName());
+        }
+        return keepEnabledPackages.contains(packageName);
+    }
+
+    /**
+     * Returns {@code true} if the given {@code packageName} is device owner or profile owner of at
+     * least one user.
+     */
+    public static boolean isProfileOrDeviceOwner(String packageName, DevicePolicyManager dpm,
+            CarUserManagerHelper um) {
+        if (dpm.isDeviceOwnerAppOnAnyUser(packageName)) {
+            return true;
+        }
+        List<UserInfo> userInfos = um.getAllUsers();
+        for (int i = 0; i < userInfos.size(); i++) {
+            ComponentName cn = dpm.getProfileOwnerAsUser(userInfos.get(i).id);
+            if (cn != null && cn.getPackageName().equals(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if the given {@code packageName} is a fallback package as defined by
+     * {@link IWebViewUpdateService}.
+     */
+    public static boolean isFallbackPackage(String packageName) {
+        try {
+            IWebViewUpdateService webviewUpdateService = IWebViewUpdateService.Stub.asInterface(
+                    ServiceManager.getService("webviewupdate"));
+            if (webviewUpdateService != null && webviewUpdateService.isFallbackPackage(
+                    packageName)) {
+                return true;
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/car/settings/applications/AppsAndNotificationsFragment.java b/src/com/android/car/settings/applications/AppsAndNotificationsFragment.java
new file mode 100644
index 0000000..a935aee
--- /dev/null
+++ b/src/com/android/car/settings/applications/AppsAndNotificationsFragment.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Shows subsettings related to apps and notifications. */
+public class AppsAndNotificationsFragment extends SettingsFragment {
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.apps_and_notifications_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/DefaultApplicationsSettingsFragment.java b/src/com/android/car/settings/applications/DefaultApplicationsSettingsFragment.java
new file mode 100644
index 0000000..3b7eb58
--- /dev/null
+++ b/src/com/android/car/settings/applications/DefaultApplicationsSettingsFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Shows the default applications settings. */
+public class DefaultApplicationsSettingsFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.default_applications_settings_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/NotificationsPreferenceController.java b/src/com/android/car/settings/applications/NotificationsPreferenceController.java
new file mode 100644
index 0000000..db7da71
--- /dev/null
+++ b/src/com/android/car/settings/applications/NotificationsPreferenceController.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ServiceManager;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Controller for preference which enables / disables showing notifications for an application.
+ */
+public class NotificationsPreferenceController extends PreferenceController<TwoStatePreference> {
+
+    private static final Logger LOG = new Logger(NotificationsPreferenceController.class);
+
+    private String mPackageName;
+    private int mUid;
+
+    @VisibleForTesting
+    INotificationManager mNotificationManager =
+            INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+
+    public NotificationsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /**
+     * Set the package info of the application.
+     */
+    public void setPackageInfo(PackageInfo packageInfo) {
+        mPackageName = packageInfo.packageName;
+        mUid = packageInfo.applicationInfo.uid;
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(isNotificationsEnabled());
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean enabled = (boolean) newValue;
+
+        try {
+            if (mNotificationManager.onlyHasDefaultChannel(mPackageName, mUid)) {
+                NotificationChannel defaultChannel =
+                        mNotificationManager.getNotificationChannelForPackage(
+                                mPackageName,
+                                mUid,
+                                NotificationChannel.DEFAULT_CHANNEL_ID,
+                                /* includeDeleted= */ true);
+                defaultChannel.setImportance(enabled ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE);
+                mNotificationManager
+                        .updateNotificationChannelForPackage(mPackageName, mUid, defaultChannel);
+            }
+            mNotificationManager.setNotificationsEnabledForPackage(mPackageName, mUid, enabled);
+        } catch (Exception e) {
+            LOG.w("Error querying notification setting for package");
+            return false;
+        }
+        return true;
+    }
+
+    private boolean isNotificationsEnabled() {
+        try {
+            return mNotificationManager.areNotificationsEnabledForPackage(mPackageName, mUid);
+        } catch (Exception e) {
+            LOG.w("Error querying notification setting for package");
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/PermissionsPreferenceController.java b/src/com/android/car/settings/applications/PermissionsPreferenceController.java
new file mode 100644
index 0000000..98d4634
--- /dev/null
+++ b/src/com/android/car/settings/applications/PermissionsPreferenceController.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.icu.text.ListFormatter;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.PermissionsSummaryHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Business logic for the permissions entry in the application details settings. */
+public class PermissionsPreferenceController extends PreferenceController<Preference> {
+
+    private static final Logger LOG = new Logger(PermissionsPreferenceController.class);
+
+    private String mPackageName;
+    private String mSummary;
+
+    public PermissionsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /**
+     * Set the packageName, which is used on the intent to open the permissions
+     * selection screen.
+     */
+    public void setPackageName(String packageName) {
+        mPackageName = packageName;
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mPackageName == null) {
+            throw new IllegalStateException(
+                    "PackageName should be set before calling this function");
+        }
+    }
+
+    @Override
+    protected void onStartInternal() {
+        PermissionsSummaryHelper.getPermissionSummary(getContext(), mPackageName,
+                mPermissionCallback);
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(getSummary());
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName);
+        try {
+            getContext().startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            LOG.w("No app can handle android.intent.action.MANAGE_APP_PERMISSIONS");
+        }
+        return true;
+    }
+
+    private CharSequence getSummary() {
+        if (TextUtils.isEmpty(mSummary)) {
+            return getContext().getString(R.string.computing_size);
+        }
+        return mSummary;
+    }
+
+    private final PermissionsSummaryHelper.PermissionsResultCallback mPermissionCallback =
+            new PermissionsSummaryHelper.PermissionsResultCallback() {
+                @Override
+                public void onPermissionSummaryResult(int standardGrantedPermissionCount,
+                        int requestedPermissionCount, int additionalGrantedPermissionCount,
+                        List<CharSequence> grantedGroupLabels) {
+                    Resources res = getContext().getResources();
+
+                    if (requestedPermissionCount == 0) {
+                        mSummary = res.getString(
+                                R.string.runtime_permissions_summary_no_permissions_requested);
+                    } else {
+                        ArrayList<CharSequence> list = new ArrayList<>(grantedGroupLabels);
+                        if (additionalGrantedPermissionCount > 0) {
+                            // N additional permissions.
+                            list.add(res.getQuantityString(
+                                    R.plurals.runtime_permissions_additional_count,
+                                    additionalGrantedPermissionCount,
+                                    additionalGrantedPermissionCount));
+                        }
+                        if (list.isEmpty()) {
+                            mSummary = res.getString(
+                                    R.string.runtime_permissions_summary_no_permissions_granted);
+                        } else {
+                            mSummary = ListFormatter.getInstance().format(list);
+                        }
+                    }
+                    refreshUi();
+                }
+            };
+}
diff --git a/src/com/android/car/settings/applications/VersionPreferenceController.java b/src/com/android/car/settings/applications/VersionPreferenceController.java
new file mode 100644
index 0000000..8df5ce0
--- /dev/null
+++ b/src/com/android/car/settings/applications/VersionPreferenceController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Business logic for the Version field in the application details page. */
+public class VersionPreferenceController extends PreferenceController<Preference> {
+
+    private PackageInfo mPackageInfo;
+
+    public VersionPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /** Set the package info which is used to get the version name. */
+    public void setPackageInfo(PackageInfo packageInfo) {
+        mPackageInfo = packageInfo;
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mPackageInfo == null) {
+            throw new IllegalStateException(
+                    "PackageInfo should be set before calling this function");
+        }
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setTitle(getContext().getString(
+                R.string.application_version_label, mPackageInfo.versionName));
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/AssistConfigBasePreferenceController.java b/src/com/android/car/settings/applications/assist/AssistConfigBasePreferenceController.java
new file mode 100644
index 0000000..1a7ea28
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/AssistConfigBasePreferenceController.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.internal.app.AssistUtils;
+
+import java.util.List;
+
+/** Common logic for preference controllers that configure the assistant's behavior. */
+public abstract class AssistConfigBasePreferenceController extends
+        PreferenceController<TwoStatePreference> {
+
+    private final SettingObserver mSettingObserver;
+    private final AssistUtils mAssistUtils;
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public AssistConfigBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAssistUtils = new AssistUtils(context);
+        mSettingObserver = new SettingObserver(getSettingUris(), this::refreshUi);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        int userId = mCarUserManagerHelper.getCurrentProcessUserId();
+        return mAssistUtils.getAssistComponentForUser(userId) != null ? AVAILABLE
+                : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mSettingObserver.register(getContext().getContentResolver(), true);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mSettingObserver.register(getContext().getContentResolver(), false);
+    }
+
+    /** Gets the Setting Uris that should be observed */
+    protected abstract List<Uri> getSettingUris();
+
+    /**
+     * Creates an observer that listens for changes to {@link Settings.Secure#ASSISTANT} as well as
+     * any other URI defined by {@link #getSettingUris()}.
+     */
+    private static class SettingObserver extends ContentObserver {
+
+        private static final Uri ASSIST_URI = Settings.Secure.getUriFor(Settings.Secure.ASSISTANT);
+        private final List<Uri> mUriList;
+        private final Runnable mSettingChangeListener;
+
+        SettingObserver(List<Uri> uriList, Runnable settingChangeListener) {
+            super(new Handler(Looper.getMainLooper()));
+            mUriList = uriList;
+            mSettingChangeListener = settingChangeListener;
+        }
+
+        /** Registers or unregisters this observer to the given content resolver. */
+        void register(ContentResolver cr, boolean register) {
+            if (register) {
+                cr.registerContentObserver(ASSIST_URI, /* notifyForDescendants= */ false,
+                        /* observer= */ this);
+                if (mUriList != null) {
+                    for (Uri uri : mUriList) {
+                        cr.registerContentObserver(uri, /* notifyForDescendants= */ false,
+                                /* observer=*/ this);
+                    }
+                }
+            } else {
+                cr.unregisterContentObserver(this);
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+
+            if (shouldUpdatePreference(uri)) {
+                mSettingChangeListener.run();
+            }
+        }
+
+        private boolean shouldUpdatePreference(Uri uri) {
+            return ASSIST_URI.equals(uri) || (mUriList != null && mUriList.contains(uri));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerEntryPreferenceController.java b/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerEntryPreferenceController.java
new file mode 100644
index 0000000..e923e7f
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerEntryPreferenceController.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.settings.applications.defaultapps.DefaultAppsPickerEntryBasePreferenceController;
+import com.android.car.settings.common.FragmentController;
+import com.android.internal.app.AssistUtils;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import java.util.Objects;
+
+/**
+ * Business logic to show the currently selected default voice input service and also link to the
+ * service settings, if it exists.
+ */
+public class DefaultVoiceInputPickerEntryPreferenceController extends
+        DefaultAppsPickerEntryBasePreferenceController {
+
+    private static final Uri ASSIST_URI = Settings.Secure.getUriFor(Settings.Secure.ASSISTANT);
+
+    private final ContentObserver mSettingObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+
+            if (ASSIST_URI.equals(uri)) {
+                refreshUi();
+            }
+        }
+    };
+
+    private final PackageManagerWrapper mPm;
+    private final VoiceInputInfoProvider mVoiceInputInfoProvider;
+    private final AssistUtils mAssistUtils;
+
+    public DefaultVoiceInputPickerEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPm = new PackageManagerWrapper(context.getPackageManager());
+        mVoiceInputInfoProvider = new VoiceInputInfoProvider(context);
+        mAssistUtils = new AssistUtils(context);
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        ComponentName currentVoiceService = VoiceInputUtils.getCurrentService(getContext());
+        ComponentName currentAssist = mAssistUtils.getAssistComponentForUser(
+                getCurrentProcessUserId());
+
+        return Objects.equals(currentAssist, currentVoiceService) ? CONDITIONALLY_UNAVAILABLE
+                : AVAILABLE;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        getContext().getContentResolver().registerContentObserver(ASSIST_URI,
+                /* notifyForDescendants= */ false, mSettingObserver);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
+    }
+
+    @Nullable
+    @Override
+    protected DefaultAppInfo getCurrentDefaultAppInfo() {
+        VoiceInputInfoProvider.VoiceInputInfo info = mVoiceInputInfoProvider.getInfoForComponent(
+                VoiceInputUtils.getCurrentService(getContext()));
+        return (info == null) ? null : new DefaultVoiceInputServiceInfo(getContext(), mPm,
+                getCurrentProcessUserId(), info, /* enabled= */ true);
+    }
+
+    @Nullable
+    @Override
+    protected Intent getSettingIntent(@Nullable DefaultAppInfo info) {
+        if (info instanceof DefaultVoiceInputServiceInfo) {
+            return ((DefaultVoiceInputServiceInfo) info).getSettingIntent();
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerFragment.java b/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerFragment.java
new file mode 100644
index 0000000..1460ade
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Shows the screen to pick the default voice input service. */
+public class DefaultVoiceInputPickerFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.default_voice_input_picker_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerPreferenceController.java b/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerPreferenceController.java
new file mode 100644
index 0000000..343c43f
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerPreferenceController.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.settings.applications.defaultapps.DefaultAppsPickerBasePreferenceController;
+import com.android.car.settings.common.FragmentController;
+import com.android.internal.app.AssistUtils;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Business logic for displaying and choosing the default voice input service. */
+public class DefaultVoiceInputPickerPreferenceController extends
+        DefaultAppsPickerBasePreferenceController {
+
+    private final AssistUtils mAssistUtils;
+    private final VoiceInputInfoProvider mVoiceInputInfoProvider;
+    private final PackageManagerWrapper mPm;
+
+    // Current assistant component name, used to restrict available voice inputs.
+    private String mAssistComponentName = null;
+
+    public DefaultVoiceInputPickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPm = new PackageManagerWrapper(context.getPackageManager());
+        mAssistUtils = new AssistUtils(context);
+        mVoiceInputInfoProvider = new VoiceInputInfoProvider(context);
+        if (Objects.equals(mAssistUtils.getAssistComponentForUser(getCurrentProcessUserId()),
+                VoiceInputUtils.getCurrentService(getContext()))) {
+            ComponentName cn = mAssistUtils.getAssistComponentForUser(getCurrentProcessUserId());
+            if (cn != null) {
+                mAssistComponentName = cn.flattenToString();
+            }
+        }
+    }
+
+    @NonNull
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        List<DefaultAppInfo> candidates = new ArrayList<>();
+        for (VoiceInputInfoProvider.VoiceInteractionInfo info :
+                mVoiceInputInfoProvider.getVoiceInteractionInfoList()) {
+            boolean enabled = TextUtils.equals(info.getComponentName().flattenToString(),
+                    mAssistComponentName);
+            candidates.add(new DefaultVoiceInputServiceInfo(getContext(), mPm,
+                    getCurrentProcessUserId(), info, enabled));
+        }
+
+        for (VoiceInputInfoProvider.VoiceRecognitionInfo info :
+                mVoiceInputInfoProvider.getVoiceRecognitionInfoList()) {
+            candidates.add(new DefaultVoiceInputServiceInfo(getContext(), mPm,
+                    getCurrentProcessUserId(), info, /* enabled= */ true));
+        }
+        return candidates;
+    }
+
+    @Override
+    protected String getCurrentDefaultKey() {
+        ComponentName cn = VoiceInputUtils.getCurrentService(getContext());
+        if (cn == null) {
+            return null;
+        }
+        return cn.flattenToString();
+    }
+
+    @Override
+    protected void setCurrentDefault(String key) {
+        ComponentName cn = ComponentName.unflattenFromString(key);
+        VoiceInputInfoProvider.VoiceInputInfo info = mVoiceInputInfoProvider.getInfoForComponent(
+                cn);
+
+        if (info instanceof VoiceInputInfoProvider.VoiceInteractionInfo) {
+            VoiceInputInfoProvider.VoiceInteractionInfo interactionInfo =
+                    (VoiceInputInfoProvider.VoiceInteractionInfo) info;
+            Settings.Secure.putString(getContext().getContentResolver(),
+                    Settings.Secure.VOICE_INTERACTION_SERVICE, key);
+            Settings.Secure.putString(getContext().getContentResolver(),
+                    Settings.Secure.VOICE_RECOGNITION_SERVICE,
+                    new ComponentName(interactionInfo.getPackageName(),
+                            interactionInfo.getRecognitionService())
+                            .flattenToString());
+        } else if (info instanceof VoiceInputInfoProvider.VoiceRecognitionInfo) {
+            Settings.Secure.putString(getContext().getContentResolver(),
+                    Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+            Settings.Secure.putString(getContext().getContentResolver(),
+                    Settings.Secure.VOICE_RECOGNITION_SERVICE, key);
+        }
+    }
+
+    @Override
+    protected boolean includeNonePreference() {
+        return false;
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/DefaultVoiceInputServiceInfo.java b/src/com/android/car/settings/applications/assist/DefaultVoiceInputServiceInfo.java
new file mode 100644
index 0000000..8682ebf
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/DefaultVoiceInputServiceInfo.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.settings.applications.assist.VoiceInputInfoProvider.VoiceInteractionInfo;
+import com.android.car.settings.applications.assist.VoiceInputInfoProvider.VoiceRecognitionInfo;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+/** An extension of {@link DefaultAppInfo} to help represent voice input services. */
+public class DefaultVoiceInputServiceInfo extends DefaultAppInfo {
+
+    private VoiceInputInfoProvider.VoiceInputInfo mInfo;
+
+    /**
+     * Constructs a {@link DefaultVoiceInputServiceInfo}
+     *
+     * @param info    a {@link VoiceInteractionInfo} or {@link VoiceRecognitionInfo} that describes
+     *                the Voice Input Service.
+     * @param enabled determines whether the service should be selectable or not.
+     */
+    public DefaultVoiceInputServiceInfo(Context context, PackageManagerWrapper pm, int userId,
+            VoiceInputInfoProvider.VoiceInputInfo info, boolean enabled) {
+        super(context, pm, userId, info.getComponentName(), /* summary= */ null, enabled);
+        mInfo = info;
+    }
+
+    @Override
+    public CharSequence loadLabel() {
+        return mInfo.getLabel();
+    }
+
+    /** Gets the intent to open the related settings component if it exists. */
+    @Nullable
+    public Intent getSettingIntent() {
+        if (mInfo.getSettingsActivityComponentName() == null) {
+            return null;
+        }
+        return new Intent(Intent.ACTION_MAIN).setComponent(
+                mInfo.getSettingsActivityComponentName());
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/ManageAssistEntryPreferenceController.java b/src/com/android/car/settings/applications/assist/ManageAssistEntryPreferenceController.java
new file mode 100644
index 0000000..36a8704
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/ManageAssistEntryPreferenceController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.car.settings.applications.defaultapps.DefaultAppEntryBasePreferenceController;
+import com.android.car.settings.common.FragmentController;
+import com.android.internal.app.AssistUtils;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+/**
+ * Business logic to show the currently selected default assist.
+ */
+public class ManageAssistEntryPreferenceController extends
+        DefaultAppEntryBasePreferenceController<Preference> {
+
+    private final AssistUtils mAssistUtils;
+    private final PackageManagerWrapper mPm;
+
+    public ManageAssistEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAssistUtils = new AssistUtils(context);
+        mPm = new PackageManagerWrapper(context.getPackageManager());
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Nullable
+    protected DefaultAppInfo getCurrentDefaultAppInfo() {
+        ComponentName cn = mAssistUtils.getAssistComponentForUser(getCurrentProcessUserId());
+        if (cn == null) {
+            return null;
+        }
+        return new DefaultAppInfo(getContext(), mPm, getCurrentProcessUserId(), cn);
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/ManageAssistFragment.java b/src/com/android/car/settings/applications/assist/ManageAssistFragment.java
new file mode 100644
index 0000000..d4fd2f5
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/ManageAssistFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Assistant management settings screen. */
+public class ManageAssistFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.manage_assist_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/ScreenshotContextPreferenceController.java b/src/com/android/car/settings/applications/assist/ScreenshotContextPreferenceController.java
new file mode 100644
index 0000000..eadad84
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/ScreenshotContextPreferenceController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Toggles the assistant's ability to use a screenshot of the screen for context. */
+public class ScreenshotContextPreferenceController extends AssistConfigBasePreferenceController {
+
+    public ScreenshotContextPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        boolean checked = Settings.Secure.getInt(getContext().getContentResolver(),
+                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1) != 0;
+        preference.setChecked(checked);
+
+        boolean contextChecked = Settings.Secure.getInt(getContext().getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1) != 0;
+        preference.setEnabled(contextChecked);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        Settings.Secure.putInt(getContext().getContentResolver(),
+                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, (boolean) newValue ? 1 : 0);
+        return true;
+    }
+
+    @Override
+    protected List<Uri> getSettingUris() {
+        return Arrays.asList(
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_SCREENSHOT_ENABLED),
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_STRUCTURE_ENABLED));
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/TextContextPreferenceController.java b/src/com/android/car/settings/applications/assist/TextContextPreferenceController.java
new file mode 100644
index 0000000..336d5f7
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/TextContextPreferenceController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+
+import java.util.Collections;
+import java.util.List;
+
+/** Toggles the assistant's ability to use the text on the screen for context. */
+public class TextContextPreferenceController extends AssistConfigBasePreferenceController {
+
+    public TextContextPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(isAssistContextEnabled());
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        Settings.Secure.putInt(getContext().getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, (boolean) newValue ? 1 : 0);
+        return true;
+    }
+
+    @Override
+    protected List<Uri> getSettingUris() {
+        return Collections.singletonList(
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_STRUCTURE_ENABLED));
+    }
+
+    private boolean isAssistContextEnabled() {
+        return Settings.Secure.getInt(getContext().getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1) != 0;
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/VoiceInputInfoProvider.java b/src/com/android/car/settings/applications/assist/VoiceInputInfoProvider.java
new file mode 100644
index 0000000..ef01f4d
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/VoiceInputInfoProvider.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
+import android.speech.RecognitionService;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.collection.ArrayMap;
+import androidx.collection.ArraySet;
+
+import com.android.car.settings.common.Logger;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Extracts the voice interaction services and voice recognition services and converts them into
+ * {@link VoiceInteractionInfo} instances and {@link VoiceRecognitionInfo} instances.
+ */
+public class VoiceInputInfoProvider {
+
+    private static final Logger LOG = new Logger(VoiceInputInfoProvider.class);
+    @VisibleForTesting
+    static final Intent VOICE_INTERACTION_SERVICE_TAG = new Intent(
+            VoiceInteractionService.SERVICE_INTERFACE);
+    @VisibleForTesting
+    static final Intent VOICE_RECOGNITION_SERVICE_TAG = new Intent(
+            RecognitionService.SERVICE_INTERFACE);
+
+    private final Context mContext;
+    private final Map<ComponentName, VoiceInputInfo> mComponentToInfoMap = new ArrayMap<>();
+    private final List<VoiceInteractionInfo> mVoiceInteractionInfoList = new ArrayList<>();
+    private final List<VoiceRecognitionInfo> mVoiceRecognitionInfoList = new ArrayList<>();
+    private final Set<ComponentName> mRecognitionServiceNames = new ArraySet<>();
+
+    public VoiceInputInfoProvider(Context context) {
+        mContext = context;
+
+        loadVoiceInteractionServices();
+        loadVoiceRecognitionServices();
+    }
+
+    /**
+     * Gets the list of voice interaction services represented as {@link VoiceInteractionInfo}
+     * instances.
+     */
+    public List<VoiceInteractionInfo> getVoiceInteractionInfoList() {
+        return mVoiceInteractionInfoList;
+    }
+
+    /**
+     * Gets the list of voice recognition services represented as {@link VoiceRecognitionInfo}
+     * instances.
+     */
+    public List<VoiceRecognitionInfo> getVoiceRecognitionInfoList() {
+        return mVoiceRecognitionInfoList;
+    }
+
+    /**
+     * Returns the appropriate {@link VoiceInteractionInfo} or {@link VoiceRecognitionInfo} based on
+     * the provided {@link ComponentName}.
+     *
+     * @return {@link VoiceInputInfo} if it exists for the component name, null otherwise.
+     */
+    @Nullable
+    public VoiceInputInfo getInfoForComponent(ComponentName key) {
+        return mComponentToInfoMap.getOrDefault(key, null);
+    }
+
+    private void loadVoiceInteractionServices() {
+        List<ResolveInfo> mAvailableVoiceInteractionServices =
+                mContext.getPackageManager().queryIntentServices(VOICE_INTERACTION_SERVICE_TAG,
+                        PackageManager.GET_META_DATA);
+
+        for (ResolveInfo resolveInfo : mAvailableVoiceInteractionServices) {
+            VoiceInteractionServiceInfo interactionServiceInfo = new VoiceInteractionServiceInfo(
+                    mContext.getPackageManager(), resolveInfo.serviceInfo);
+            if (interactionServiceInfo.getParseError() != null) {
+                LOG.w("Error in VoiceInteractionService " + resolveInfo.serviceInfo.packageName
+                        + "/" + resolveInfo.serviceInfo.name + ": "
+                        + interactionServiceInfo.getParseError());
+                continue;
+            }
+            VoiceInteractionInfo voiceInteractionInfo = new VoiceInteractionInfo(mContext,
+                    interactionServiceInfo);
+            mVoiceInteractionInfoList.add(voiceInteractionInfo);
+            if (interactionServiceInfo.getRecognitionService() != null) {
+                mRecognitionServiceNames.add(new ComponentName(resolveInfo.serviceInfo.packageName,
+                        interactionServiceInfo.getRecognitionService()));
+            }
+            mComponentToInfoMap.put(new ComponentName(resolveInfo.serviceInfo.packageName,
+                    resolveInfo.serviceInfo.name), voiceInteractionInfo);
+        }
+        Collections.sort(mVoiceInteractionInfoList);
+    }
+
+    private void loadVoiceRecognitionServices() {
+        List<ResolveInfo> mAvailableRecognitionServices =
+                mContext.getPackageManager().queryIntentServices(VOICE_RECOGNITION_SERVICE_TAG,
+                        PackageManager.GET_META_DATA);
+        for (ResolveInfo resolveInfo : mAvailableRecognitionServices) {
+            ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
+                    resolveInfo.serviceInfo.name);
+
+            VoiceRecognitionInfo voiceRecognitionInfo = new VoiceRecognitionInfo(mContext,
+                    resolveInfo.serviceInfo);
+            mVoiceRecognitionInfoList.add(voiceRecognitionInfo);
+            mRecognitionServiceNames.add(componentName);
+            mComponentToInfoMap.put(componentName, voiceRecognitionInfo);
+        }
+        Collections.sort(mVoiceRecognitionInfoList);
+    }
+
+    /**
+     * Base object used to represent {@link VoiceInteractionInfo} and {@link VoiceRecognitionInfo}.
+     */
+    abstract static class VoiceInputInfo implements Comparable {
+        private final Context mContext;
+        private final ServiceInfo mServiceInfo;
+
+        VoiceInputInfo(Context context, ServiceInfo serviceInfo) {
+            mContext = context;
+            mServiceInfo = serviceInfo;
+        }
+
+        protected Context getContext() {
+            return mContext;
+        }
+
+        protected ServiceInfo getServiceInfo() {
+            return mServiceInfo;
+        }
+
+        @Override
+        public int compareTo(Object o) {
+            return getTag().toString().compareTo(((VoiceInputInfo) o).getTag().toString());
+        }
+
+        /**
+         * Returns the {@link ComponentName} which represents the settings activity, if it exists.
+         */
+        @Nullable
+        ComponentName getSettingsActivityComponentName() {
+            String activity = getSettingsActivity();
+            return (activity != null) ? new ComponentName(mServiceInfo.packageName, activity)
+                    : null;
+        }
+
+        /** Returns the package name for the service represented by this {@link VoiceInputInfo}. */
+        String getPackageName() {
+            return mServiceInfo.packageName;
+        }
+
+        /**
+         * Returns the component name for the service represented by this {@link VoiceInputInfo}.
+         */
+        ComponentName getComponentName() {
+            return new ComponentName(mServiceInfo.packageName, mServiceInfo.name);
+        }
+
+        /**
+         * Returns the label to describe the service represented by this {@link VoiceInputInfo}.
+         */
+        abstract CharSequence getLabel();
+
+        /**
+         * The string representation of the settings activity for the service represented by this
+         * {@link VoiceInputInfo}.
+         */
+        protected abstract String getSettingsActivity();
+
+        /**
+         * Returns a tag used to determine the sort order of the {@link VoiceInputInfo} instances.
+         */
+        protected CharSequence getTag() {
+            return mServiceInfo.loadLabel(mContext.getPackageManager());
+        }
+    }
+
+    /** An object to represent {@link VoiceInteractionService} instances. */
+    static class VoiceInteractionInfo extends VoiceInputInfo {
+        private final VoiceInteractionServiceInfo mInteractionServiceInfo;
+
+        VoiceInteractionInfo(Context context, VoiceInteractionServiceInfo info) {
+            super(context, info.getServiceInfo());
+
+            mInteractionServiceInfo = info;
+        }
+
+        /** Returns the recognition service associated with this {@link VoiceInteractionService}. */
+        String getRecognitionService() {
+            return mInteractionServiceInfo.getRecognitionService();
+        }
+
+        @Override
+        protected String getSettingsActivity() {
+            return mInteractionServiceInfo.getSettingsActivity();
+        }
+
+        @Override
+        CharSequence getLabel() {
+            return getServiceInfo().applicationInfo.loadLabel(getContext().getPackageManager());
+        }
+    }
+
+    /** An object to represent {@link RecognitionService} instances. */
+    static class VoiceRecognitionInfo extends VoiceInputInfo {
+
+        VoiceRecognitionInfo(Context context, ServiceInfo serviceInfo) {
+            super(context, serviceInfo);
+        }
+
+        @Override
+        protected String getSettingsActivity() {
+            return getServiceSettingsActivity(getServiceInfo());
+        }
+
+        @Override
+        CharSequence getLabel() {
+            return getTag();
+        }
+
+        private String getServiceSettingsActivity(ServiceInfo serviceInfo) {
+            XmlResourceParser parser = null;
+            String settingActivity = null;
+            try {
+                parser = serviceInfo.loadXmlMetaData(getContext().getPackageManager(),
+                        RecognitionService.SERVICE_META_DATA);
+                if (parser == null) {
+                    throw new XmlPullParserException(
+                            "No " + RecognitionService.SERVICE_META_DATA + " meta-data for "
+                                    + serviceInfo.packageName);
+                }
+
+                Resources res = getContext().getPackageManager().getResourcesForApplication(
+                        serviceInfo.applicationInfo);
+
+                AttributeSet attrs = Xml.asAttributeSet(parser);
+
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && type != XmlPullParser.START_TAG) {
+                    continue;
+                }
+
+                String nodeName = parser.getName();
+                if (!"recognition-service".equals(nodeName)) {
+                    throw new XmlPullParserException(
+                            "Meta-data does not start with recognition-service tag");
+                }
+
+                TypedArray array = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.RecognitionService);
+                settingActivity = array.getString(
+                        com.android.internal.R.styleable.RecognitionService_settingsActivity);
+                array.recycle();
+            } catch (XmlPullParserException e) {
+                LOG.e("error parsing recognition service meta-data", e);
+            } catch (IOException e) {
+                LOG.e("error parsing recognition service meta-data", e);
+            } catch (PackageManager.NameNotFoundException e) {
+                LOG.e("error parsing recognition service meta-data", e);
+            } finally {
+                if (parser != null) parser.close();
+            }
+
+            return settingActivity;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/assist/VoiceInputUtils.java b/src/com/android/car/settings/applications/assist/VoiceInputUtils.java
new file mode 100644
index 0000000..0098fd8
--- /dev/null
+++ b/src/com/android/car/settings/applications/assist/VoiceInputUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+/** Utilities to help interact with voice input services. */
+final class VoiceInputUtils {
+
+    private VoiceInputUtils() {
+    }
+
+    /**
+     * Chooses the current service based on the current voice interaction service and current
+     * recognizer service.
+     */
+    static ComponentName getCurrentService(Context context) {
+        ComponentName currentVoiceInteraction = getComponentNameOrNull(context,
+                Settings.Secure.VOICE_INTERACTION_SERVICE);
+        ComponentName currentVoiceRecognizer = getComponentNameOrNull(context,
+                Settings.Secure.VOICE_RECOGNITION_SERVICE);
+
+        if (currentVoiceInteraction != null) {
+            return currentVoiceInteraction;
+        } else if (currentVoiceRecognizer != null) {
+            return currentVoiceRecognizer;
+        } else {
+            return null;
+        }
+    }
+
+    private static ComponentName getComponentNameOrNull(Context context, String secureSettingKey) {
+        String currentSetting = Settings.Secure.getString(context.getContentResolver(),
+                secureSettingKey);
+        if (!TextUtils.isEmpty(currentSetting)) {
+            return ComponentName.unflattenFromString(currentSetting);
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAppEntryBasePreferenceController.java b/src/com/android/car/settings/applications/defaultapps/DefaultAppEntryBasePreferenceController.java
new file mode 100644
index 0000000..a686785
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAppEntryBasePreferenceController.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.DefaultAppInfo;
+
+/**
+ * Base preference which handles the logic to display the currently selected default app.
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller expects to
+ *            operate.
+ */
+public abstract class DefaultAppEntryBasePreferenceController<V extends Preference> extends
+        PreferenceController<V> {
+
+    private static final Logger LOG = new Logger(
+            DefaultAppEntryBasePreferenceController.class);
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public DefaultAppEntryBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected void updateState(V preference) {
+        CharSequence defaultAppLabel = getDefaultAppLabel();
+        if (!TextUtils.isEmpty(defaultAppLabel)) {
+            preference.setSummary(defaultAppLabel);
+            DefaultAppUtils.setSafeIcon(preference, getDefaultAppIcon(),
+                    getContext().getResources().getInteger(R.integer.default_app_safe_icon_size));
+        } else {
+            LOG.d("No default app");
+            preference.setSummary(R.string.app_list_preference_none);
+            preference.setIcon(null);
+        }
+    }
+
+    /** Specifies the currently selected default app. */
+    @Nullable
+    protected abstract DefaultAppInfo getCurrentDefaultAppInfo();
+
+    /** Gets the current process user id. */
+    protected int getCurrentProcessUserId() {
+        return mCarUserManagerHelper.getCurrentProcessUserId();
+    }
+
+    private Drawable getDefaultAppIcon() {
+        DefaultAppInfo app = getCurrentDefaultAppInfo();
+        if (app != null) {
+            return app.loadIcon();
+        }
+        return null;
+    }
+
+    private CharSequence getDefaultAppLabel() {
+        DefaultAppInfo app = getCurrentDefaultAppInfo();
+        if (app != null) {
+            return app.loadLabel();
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAppUtils.java b/src/com/android/car/settings/applications/defaultapps/DefaultAppUtils.java
new file mode 100644
index 0000000..2910860
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAppUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
+
+import androidx.preference.Preference;
+
+/** Utilities related to default apps. */
+public class DefaultAppUtils {
+
+    private DefaultAppUtils() {}
+
+    /**
+     * Sets the preference icon with a drawable that is scaled down to to avoid crashing Settings if
+     * it's too big.
+     */
+    public static void setSafeIcon(Preference pref, Drawable icon, int maxDimension) {
+        Drawable safeIcon = icon;
+        if ((icon != null) && !(icon instanceof VectorDrawable)) {
+            safeIcon = getSafeDrawable(icon, maxDimension);
+        }
+        pref.setIcon(safeIcon);
+    }
+
+    /**
+     * Gets a drawable with a limited size to avoid crashing Settings if it's too big.
+     *
+     * @param original     original drawable, typically an app icon.
+     * @param maxDimension maximum width/height, in pixels.
+     */
+    private static Drawable getSafeDrawable(Drawable original, int maxDimension) {
+        int actualWidth = original.getMinimumWidth();
+        int actualHeight = original.getMinimumHeight();
+
+        if (actualWidth <= maxDimension && actualHeight <= maxDimension) {
+            return original;
+        }
+
+        float scaleWidth = ((float) maxDimension) / actualWidth;
+        float scaleHeight = ((float) maxDimension) / actualHeight;
+        float scale = Math.min(scaleWidth, scaleHeight);
+        int width = (int) (actualWidth * scale);
+        int height = (int) (actualHeight * scale);
+
+        Bitmap bitmap;
+        if (original instanceof BitmapDrawable) {
+            bitmap = Bitmap.createScaledBitmap(((BitmapDrawable) original).getBitmap(), width,
+                    height, false);
+        } else {
+            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            original.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            original.draw(canvas);
+        }
+        return new BitmapDrawable(null, bitmap);
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerBasePreferenceController.java b/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerBasePreferenceController.java
new file mode 100644
index 0000000..5e38a6e
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerBasePreferenceController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.DefaultAppInfo;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Defines the shared logic in picking a default application. */
+public abstract class DefaultAppsPickerBasePreferenceController extends
+        PreferenceController<PreferenceGroup> implements Preference.OnPreferenceClickListener {
+
+    private static final Logger LOG = new Logger(DefaultAppsPickerBasePreferenceController.class);
+    private static final String DIALOG_KEY_ARG = "key_arg";
+    protected static final String NONE_PREFERENCE_KEY = "";
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final Map<String, DefaultAppInfo> mDefaultAppInfoMap = new HashMap<>();
+    private final ConfirmationDialogFragment.ConfirmListener mConfirmListener = arguments -> {
+        setCurrentDefault(arguments.getString(DIALOG_KEY_ARG));
+        refreshUi();
+    };
+    private List<DefaultAppInfo> mCurrentCandidates;
+
+    public DefaultAppsPickerBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        ConfirmationDialogFragment.resetListeners(
+                (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
+                        ConfirmationDialogFragment.TAG),
+                mConfirmListener,
+                /* rejectListener= */ null);
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        List<DefaultAppInfo> defaultAppInfos = getCandidates();
+        if (!equalToCurrentCandidates(defaultAppInfos)) {
+            mCurrentCandidates = defaultAppInfos;
+            preferenceGroup.removeAll();
+            if (includeNonePreference()) {
+                preferenceGroup.addPreference(createNonePreference());
+            }
+            if (mCurrentCandidates != null) {
+                for (DefaultAppInfo info : mCurrentCandidates) {
+                    mDefaultAppInfoMap.put(info.getKey(), info);
+
+                    Preference preference = new Preference(getContext());
+                    bindPreference(preference, info);
+                    getPreference().addPreference(preference);
+                }
+            } else {
+                LOG.i("no candidate provided");
+            }
+        }
+
+        // This is done separately from above, since the summary can change without changing the
+        // list of candidates.
+        for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
+            Preference preference = preferenceGroup.getPreference(i);
+            String newPreferenceSummary = TextUtils.equals(preference.getKey(),
+                    getCurrentDefaultKey()) ? getContext().getString(
+                    R.string.default_app_selected_app) : "";
+            preference.setSummary(newPreferenceSummary);
+        }
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        String selectedKey = preference.getKey();
+        if (TextUtils.equals(selectedKey, getCurrentDefaultKey())) {
+            return false;
+        }
+
+        CharSequence message = getConfirmationMessage(mDefaultAppInfoMap.get(selectedKey));
+        if (!TextUtils.isEmpty(message)) {
+            ConfirmationDialogFragment dialogFragment =
+                    new ConfirmationDialogFragment.Builder(getContext())
+                            .setMessage(message.toString())
+                            .setPositiveButton(android.R.string.ok, mConfirmListener)
+                            .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null)
+                            .addArgumentString(DIALOG_KEY_ARG, selectedKey)
+                            .build();
+            getFragmentController().showDialog(dialogFragment, ConfirmationDialogFragment.TAG);
+        } else {
+            setCurrentDefault(selectedKey);
+            refreshUi();
+        }
+        return true;
+    }
+
+    /** Modifies the preference based on the information provided. */
+    protected void bindPreference(Preference preference, DefaultAppInfo info) {
+        preference.setTitle(info.loadLabel());
+        preference.setKey(info.getKey());
+        preference.setEnabled(info.enabled);
+        preference.setOnPreferenceClickListener(this);
+        DefaultAppUtils.setSafeIcon(preference, info.loadIcon(),
+                getContext().getResources().getInteger(R.integer.default_app_safe_icon_size));
+    }
+
+    /** Gets all of the candidates that should be considered when choosing a default application. */
+    @NonNull
+    protected abstract List<DefaultAppInfo> getCandidates();
+
+    /** Gets the key of the currently selected candidate. */
+    protected abstract String getCurrentDefaultKey();
+
+    /**
+     * Sets the key of the currently selected candidate.
+     *
+     * @param key represents the key from {@link DefaultAppInfo} which should mark the default
+     *            application.
+     */
+    protected abstract void setCurrentDefault(String key);
+
+    /**
+     * Defines the warning dialog message to be shown when a default app is selected.
+     */
+    protected CharSequence getConfirmationMessage(DefaultAppInfo info) {
+        return null;
+    }
+
+    /** Gets the current process user id. */
+    protected int getCurrentProcessUserId() {
+        return mCarUserManagerHelper.getCurrentProcessUserId();
+    }
+
+    /**
+     * Determines whether the list of default apps should include "none". Implementation classes can
+     * override this value to {@code false} in order to remove the "none" preference.
+     */
+    protected boolean includeNonePreference() {
+        return true;
+    }
+
+    private Preference createNonePreference() {
+        Preference nonePreference = new Preference(getContext());
+        nonePreference.setKey(NONE_PREFERENCE_KEY);
+        nonePreference.setTitle(R.string.app_list_preference_none);
+        nonePreference.setOnPreferenceClickListener(this);
+        nonePreference.setIcon(R.drawable.ic_remove_circle);
+        return nonePreference;
+    }
+
+    /**
+     * Check that the provided {@link DefaultAppInfo} list is equivalent to the current list of
+     * candidates.
+     */
+    private boolean equalToCurrentCandidates(@NonNull List<DefaultAppInfo> defaultAppInfos) {
+        if (mCurrentCandidates == null) {
+            return false;
+        }
+
+        Set<String> keys = new HashSet<>();
+        for (DefaultAppInfo info : mCurrentCandidates) {
+            keys.add(info.getKey());
+        }
+        for (DefaultAppInfo info : defaultAppInfos) {
+            if (!keys.remove(info.getKey())) {
+                return false;
+            }
+        }
+        return keys.isEmpty();
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerEntryBasePreferenceController.java b/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerEntryBasePreferenceController.java
new file mode 100644
index 0000000..e64df67
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerEntryBasePreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.applications.DefaultAppInfo;
+
+/**
+ * Base preference which handles the logic to display the currently selected default app as well as
+ * an option to navigate to the settings of the selected default app.
+ */
+public abstract class DefaultAppsPickerEntryBasePreferenceController extends
+        DefaultAppEntryBasePreferenceController<ButtonPreference> {
+
+    public DefaultAppsPickerEntryBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ButtonPreference> getPreferenceType() {
+        return ButtonPreference.class;
+    }
+
+    @Override
+    protected void updateState(ButtonPreference preference) {
+        super.updateState(preference);
+
+        Intent intent = getSettingIntent(getCurrentDefaultAppInfo());
+        preference.showAction(intent != null);
+        if (intent != null) {
+            preference.setOnButtonClickListener(p -> getContext().startActivity(intent));
+        }
+    }
+
+    /**
+     * Returns an optional intent that will be launched when clicking the secondary action icon.
+     */
+    @Nullable
+    protected Intent getSettingIntent(@Nullable DefaultAppInfo info) {
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerEntryPreferenceController.java b/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerEntryPreferenceController.java
new file mode 100644
index 0000000..374afef
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerEntryPreferenceController.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.internal.app.AssistUtils;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import java.util.List;
+
+/**
+ * Business logic to show the currently selected default assistant and also show the assistant
+ * settings, if it exists.
+ */
+public class DefaultAssistantPickerEntryPreferenceController extends
+        DefaultAppsPickerEntryBasePreferenceController {
+
+    @VisibleForTesting
+    static final Intent ASSISTANT_SERVICE = new Intent(
+            VoiceInteractionService.SERVICE_INTERFACE);
+
+    private final AssistUtils mAssistUtils;
+    private final PackageManagerWrapper mPm;
+
+    public DefaultAssistantPickerEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAssistUtils = new AssistUtils(context);
+        mPm = new PackageManagerWrapper(context.getPackageManager());
+    }
+
+    @Nullable
+    @Override
+    protected DefaultAppInfo getCurrentDefaultAppInfo() {
+        ComponentName cn = mAssistUtils.getAssistComponentForUser(getCurrentProcessUserId());
+        if (cn == null) {
+            return null;
+        }
+        return new DefaultAppInfo(getContext(), mPm, getCurrentProcessUserId(), cn);
+    }
+
+    @Nullable
+    @Override
+    protected Intent getSettingIntent(@Nullable DefaultAppInfo info) {
+        ComponentName cn = mAssistUtils.getAssistComponentForUser(getCurrentProcessUserId());
+        if (cn == null) {
+            return null;
+        }
+
+        Intent probe = ASSISTANT_SERVICE.setPackage(cn.getPackageName());
+        PackageManager pm = mPm.getPackageManager();
+        List<ResolveInfo> services = pm.queryIntentServices(probe, PackageManager.GET_META_DATA);
+        if (services == null || services.isEmpty()) {
+            return null;
+        }
+
+        String activity = getAssistSettingsActivity(pm, services.get(0));
+        if (activity == null) {
+            return null;
+        }
+
+        return new Intent(Intent.ACTION_MAIN).setComponent(
+                new ComponentName(cn.getPackageName(), activity));
+    }
+
+    private String getAssistSettingsActivity(PackageManager pm, ResolveInfo resolveInfo) {
+        VoiceInteractionServiceInfo voiceInfo = new VoiceInteractionServiceInfo(pm,
+                resolveInfo.serviceInfo);
+        if (!voiceInfo.getSupportsAssist()) {
+            return null;
+        }
+        return voiceInfo.getSettingsActivity();
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerFragment.java b/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerFragment.java
new file mode 100644
index 0000000..78c1473
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Shows the option to choose the default assistant. */
+public class DefaultAssistantPickerFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.default_assistant_picker_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerPreferenceController.java b/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerPreferenceController.java
new file mode 100644
index 0000000..be3ebed
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerPreferenceController.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
+import android.speech.RecognitionService;
+import android.text.TextUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.internal.app.AssistUtils;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Business logic for displaying and choosing the default assistant. */
+public class DefaultAssistantPickerPreferenceController extends
+        DefaultAppsPickerBasePreferenceController {
+
+    private static final Logger LOG = new Logger(DefaultAssistantPickerPreferenceController.class);
+    @VisibleForTesting
+    static final Intent ASSIST_SERVICE_PROBE = new Intent(
+            VoiceInteractionService.SERVICE_INTERFACE);
+    @VisibleForTesting
+    static final Intent ASSIST_ACTIVITY_PROBE = new Intent(Intent.ACTION_ASSIST);
+
+    private final AssistUtils mAssistUtils;
+    private final Map<String, Info> mAvailableAssistants = new LinkedHashMap<>();
+    private final PackageManagerWrapper mPm;
+
+    public DefaultAssistantPickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAssistUtils = new AssistUtils(context);
+        mPm = new PackageManagerWrapper(getContext().getPackageManager());
+    }
+
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        mAvailableAssistants.clear();
+
+        List<Info> assistants = new ArrayList<>();
+        PackageManager pm = getContext().getPackageManager();
+        addAssistServices(pm, assistants);
+        addAssistActivities(pm, assistants);
+
+        // Return things to show.
+        Set<String> packages = new HashSet<>();
+        List<DefaultAppInfo> candidates = new ArrayList<>();
+        for (Info info : assistants) {
+            if (packages.contains(info.getComponentName().getPackageName())) {
+                continue;
+            }
+            packages.add(info.getComponentName().getPackageName());
+            candidates.add(new DefaultAppInfo(getContext(), mPm, getCurrentProcessUserId(),
+                    info.getComponentName()));
+            mAvailableAssistants.put(info.getComponentName().getPackageName(), info);
+        }
+
+        return candidates;
+    }
+
+    @Override
+    protected CharSequence getConfirmationMessage(DefaultAppInfo info) {
+        if (info == null) {
+            return null;
+        }
+        return getContext().getString(R.string.assistant_security_warning);
+    }
+
+    @Override
+    protected String getCurrentDefaultKey() {
+        ComponentName cn = getCurrentAssistant();
+        if (cn != null) {
+            return new DefaultAppInfo(getContext(), mPm, getCurrentProcessUserId(), cn).getKey();
+        }
+        return DefaultAppsPickerBasePreferenceController.NONE_PREFERENCE_KEY;
+    }
+
+    @Override
+    protected void setCurrentDefault(String key) {
+        if (TextUtils.isEmpty(key)) {
+            setAssistNone();
+            return;
+        }
+
+        ComponentName cn = ComponentName.unflattenFromString(key);
+        Info info = mAvailableAssistants.getOrDefault(cn.getPackageName(), null);
+        if (info == null) {
+            setAssistNone();
+            return;
+        }
+
+        if (info.isVoiceInteractionService()) {
+            setAssistService(info);
+        } else {
+            setAssistActivity(info);
+        }
+    }
+
+    private ComponentName getCurrentAssistant() {
+        return mAssistUtils.getAssistComponentForUser(getCurrentProcessUserId());
+    }
+
+    private void addAssistServices(PackageManager pm, List<Info> availableAssistants) {
+        List<ResolveInfo> services = pm.queryIntentServices(ASSIST_SERVICE_PROBE,
+                PackageManager.GET_META_DATA);
+        for (ResolveInfo resolveInfo : services) {
+            VoiceInteractionServiceInfo voiceInteractionServiceInfo =
+                    new VoiceInteractionServiceInfo(pm, resolveInfo.serviceInfo);
+            if (!voiceInteractionServiceInfo.getSupportsAssist()) {
+                continue;
+            }
+
+            Info info = new Info(new ComponentName(resolveInfo.serviceInfo.packageName,
+                    resolveInfo.serviceInfo.name), voiceInteractionServiceInfo);
+            availableAssistants.add(info);
+        }
+    }
+
+    private void addAssistActivities(PackageManager pm, List<Info> availableAssistants) {
+        List<ResolveInfo> activities = pm.queryIntentActivities(ASSIST_ACTIVITY_PROBE,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        for (ResolveInfo resolveInfo : activities) {
+            Info info = new Info(new ComponentName(resolveInfo.activityInfo.packageName,
+                    resolveInfo.activityInfo.name));
+            availableAssistants.add(info);
+        }
+    }
+
+    private void setAssistNone() {
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ASSISTANT, "");
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
+    }
+
+    private void setAssistService(Info serviceInfo) {
+        String serviceComponentName = serviceInfo.getComponentName().flattenToShortString();
+        String serviceRecognizerName = new ComponentName(
+                serviceInfo.getComponentName().getPackageName(),
+                serviceInfo.getVoiceInteractionServiceInfo().getRecognitionService())
+                .flattenToShortString();
+
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ASSISTANT, serviceComponentName);
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName);
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName);
+    }
+
+    private void setAssistActivity(Info activityInfo) {
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ASSISTANT, activityInfo.getComponentName().flattenToShortString());
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
+    }
+
+    private String getDefaultRecognizer() {
+        ResolveInfo resolveInfo = getContext().getPackageManager().resolveService(
+                new Intent(RecognitionService.SERVICE_INTERFACE),
+                PackageManager.GET_META_DATA);
+        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+            LOG.w("Unable to resolve default voice recognition service.");
+            return "";
+        }
+
+        return new ComponentName(resolveInfo.serviceInfo.packageName,
+                resolveInfo.serviceInfo.name).flattenToShortString();
+    }
+
+    private static class Info {
+        private final ComponentName mComponentName;
+        private final VoiceInteractionServiceInfo mVoiceInteractionServiceInfo;
+
+        Info(ComponentName component) {
+            mComponentName = component;
+            mVoiceInteractionServiceInfo = null;
+        }
+
+        Info(ComponentName component, VoiceInteractionServiceInfo voiceInteractionServiceInfo) {
+            mComponentName = component;
+            mVoiceInteractionServiceInfo = voiceInteractionServiceInfo;
+        }
+
+        /** Returns {@code true} if this object represents a voice interaction service. */
+        public boolean isVoiceInteractionService() {
+            return mVoiceInteractionServiceInfo != null;
+        }
+
+        /** Returns the component name. */
+        public ComponentName getComponentName() {
+            return mComponentName;
+        }
+
+        /** Returns the voice interaction service info. */
+        public VoiceInteractionServiceInfo getVoiceInteractionServiceInfo() {
+            return mVoiceInteractionServiceInfo;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerEntryPreferenceController.java b/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerEntryPreferenceController.java
new file mode 100644
index 0000000..b070466
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerEntryPreferenceController.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.provider.Settings;
+import android.service.autofill.AutofillService;
+import android.service.autofill.AutofillServiceInfo;
+import android.text.TextUtils;
+import android.view.autofill.AutofillManager;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import java.util.List;
+
+/** Business logic for displaying the currently selected autofill app. */
+public class DefaultAutofillPickerEntryPreferenceController extends
+        DefaultAppsPickerEntryBasePreferenceController {
+
+    private static final Logger LOG = new Logger(
+            DefaultAutofillPickerEntryPreferenceController.class);
+    private final AutofillManager mAutofillManager;
+    private final PackageManagerWrapper mPm;
+
+    public DefaultAutofillPickerEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAutofillManager = context.getSystemService(AutofillManager.class);
+        mPm = new PackageManagerWrapper(context.getPackageManager());
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (mAutofillManager != null && mAutofillManager.isAutofillSupported()) {
+            return AVAILABLE;
+        }
+        return UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Nullable
+    @Override
+    protected DefaultAppInfo getCurrentDefaultAppInfo() {
+        String flattenComponent = Settings.Secure.getString(getContext().getContentResolver(),
+                Settings.Secure.AUTOFILL_SERVICE);
+        if (!TextUtils.isEmpty(flattenComponent)) {
+            DefaultAppInfo appInfo = new DefaultAppInfo(getContext(), mPm,
+                    getCurrentProcessUserId(), ComponentName.unflattenFromString(flattenComponent));
+            return appInfo;
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    protected Intent getSettingIntent(@Nullable DefaultAppInfo info) {
+        if (info == null) {
+            return null;
+        }
+
+        Intent intent = new Intent(AutofillService.SERVICE_INTERFACE);
+        List<ResolveInfo> resolveInfos = getContext().getPackageManager().queryIntentServices(
+                intent, PackageManager.GET_META_DATA);
+
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            String flattenKey = new ComponentName(serviceInfo.packageName,
+                    serviceInfo.name).flattenToString();
+            if (TextUtils.equals(info.getKey(), flattenKey)) {
+                String settingsActivity;
+                try {
+                    settingsActivity = new AutofillServiceInfo(getContext(), serviceInfo)
+                            .getSettingsActivity();
+                } catch (SecurityException e) {
+                    // Service does not declare the proper permission, ignore it.
+                    LOG.w("Error getting info for " + serviceInfo + ": " + e);
+                    continue;
+                }
+                if (TextUtils.isEmpty(settingsActivity)) {
+                    continue;
+                }
+                return new Intent(Intent.ACTION_MAIN).setComponent(
+                        new ComponentName(serviceInfo.packageName, settingsActivity));
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerFragment.java b/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerFragment.java
new file mode 100644
index 0000000..5a97fea
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Shows the option to choose the default autofill service. */
+public class DefaultAutofillPickerFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.default_autofill_picker_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerPreferenceController.java b/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerPreferenceController.java
new file mode 100644
index 0000000..0382352
--- /dev/null
+++ b/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerPreferenceController.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import android.Manifest;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.service.autofill.AutofillService;
+import android.text.Html;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Business logic for displaying and choosing the default autofill service. */
+public class DefaultAutofillPickerPreferenceController extends
+        DefaultAppsPickerBasePreferenceController {
+
+    private final PackageManagerWrapper mPm;
+
+    public DefaultAutofillPickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPm = new PackageManagerWrapper(context.getPackageManager());
+    }
+
+    @NonNull
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        List<DefaultAppInfo> candidates = new ArrayList<>();
+        List<ResolveInfo> resolveInfos = mPm.queryIntentServices(
+                new Intent(AutofillService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
+        for (ResolveInfo info : resolveInfos) {
+            String permission = info.serviceInfo.permission;
+            if (Manifest.permission.BIND_AUTOFILL_SERVICE.equals(permission)) {
+                candidates.add(new DefaultAppInfo(getContext(), mPm, getCurrentProcessUserId(),
+                        new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name)));
+            }
+        }
+        return candidates;
+    }
+
+    @Override
+    protected String getCurrentDefaultKey() {
+        String setting = Settings.Secure.getString(getContext().getContentResolver(),
+                Settings.Secure.AUTOFILL_SERVICE);
+        if (setting != null) {
+            ComponentName componentName = ComponentName.unflattenFromString(setting);
+            if (componentName != null) {
+                return componentName.flattenToString();
+            }
+        }
+        return DefaultAppsPickerBasePreferenceController.NONE_PREFERENCE_KEY;
+    }
+
+    @Override
+    protected void setCurrentDefault(String key) {
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.AUTOFILL_SERVICE, key);
+    }
+
+    @Override
+    @Nullable
+    protected CharSequence getConfirmationMessage(DefaultAppInfo info) {
+        if (info == null) {
+            return null;
+        }
+
+        CharSequence appName = info.loadLabel();
+        String message = getContext().getString(R.string.autofill_confirmation_message, appName);
+        return Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY);
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/AppLaunchSettingsBasePreferenceController.java b/src/com/android/car/settings/applications/managedomainurls/AppLaunchSettingsBasePreferenceController.java
new file mode 100644
index 0000000..40adda3
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/AppLaunchSettingsBasePreferenceController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.List;
+
+/**
+ * Shared logic for preference controllers related to app launch settings.
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller
+ *            expects to operate.
+ */
+public abstract class AppLaunchSettingsBasePreferenceController<V extends Preference> extends
+        PreferenceController<V> {
+
+    @VisibleForTesting
+    static final Intent sBrowserIntent = new Intent()
+            .setAction(Intent.ACTION_VIEW)
+            .addCategory(Intent.CATEGORY_BROWSABLE)
+            .setData(Uri.parse("http:"));
+
+    private final PackageManager mPm;
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    private String mPackageName;
+    private ApplicationsState.AppEntry mAppEntry;
+
+    public AppLaunchSettingsBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPm = context.getPackageManager();
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    /** Sets the app entry associated with this settings screen. */
+    public void setAppEntry(ApplicationsState.AppEntry entry) {
+        mAppEntry = entry;
+    }
+
+    /** Returns the app entry. */
+    public ApplicationsState.AppEntry getAppEntry() {
+        return mAppEntry;
+    }
+
+    /** Returns the package name. */
+    public String getPackageName() {
+        return mAppEntry.info.packageName;
+    }
+
+    /** Returns the current user id. */
+    protected int getCurrentUserId() {
+        return mCarUserManagerHelper.getCurrentProcessUserId();
+    }
+
+    /** Returns {@code true} if the current package is a browser app. */
+    protected boolean isBrowserApp() {
+        sBrowserIntent.setPackage(getPackageName());
+        List<ResolveInfo> list = mPm.queryIntentActivitiesAsUser(sBrowserIntent,
+                PackageManager.MATCH_ALL, getCurrentUserId());
+        for (ResolveInfo info : list) {
+            if (info.activityInfo != null && info.handleAllWebDataURI) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/AppLinkStatePreferenceController.java b/src/com/android/car/settings/applications/managedomainurls/AppLinkStatePreferenceController.java
new file mode 100644
index 0000000..310d14b
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/AppLinkStatePreferenceController.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+
+/**
+ * Business logic to define how the app should handle related domain links (whether related domain
+ * links should be opened always, never, or after asking).
+ */
+public class AppLinkStatePreferenceController extends
+        AppLaunchSettingsBasePreferenceController<ListPreference> {
+
+    private static final Logger LOG = new Logger(AppLinkStatePreferenceController.class);
+
+    private final PackageManager mPm;
+    private boolean mHasDomainUrls;
+
+    public AppLinkStatePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPm = context.getPackageManager();
+    }
+
+    @Override
+    protected Class<ListPreference> getPreferenceType() {
+        return ListPreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mHasDomainUrls =
+                (getAppEntry().info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS)
+                        != 0;
+    }
+
+    @Override
+    protected void updateState(ListPreference preference) {
+        if (isBrowserApp()) {
+            preference.setEnabled(false);
+        } else {
+            preference.setEnabled(mHasDomainUrls);
+
+            preference.setEntries(new CharSequence[]{
+                    getContext().getString(R.string.app_link_open_always),
+                    getContext().getString(R.string.app_link_open_ask),
+                    getContext().getString(R.string.app_link_open_never),
+            });
+            preference.setEntryValues(new CharSequence[]{
+                    Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS),
+                    Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK),
+                    Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER),
+            });
+
+            if (mHasDomainUrls) {
+                int state = mPm.getIntentVerificationStatusAsUser(getPackageName(),
+                        getCurrentUserId());
+                preference.setValueIndex(linkStateToIndex(state));
+            }
+        }
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(ListPreference preference, Object newValue) {
+        if (isBrowserApp()) {
+            // We shouldn't get into this state, but if we do make sure
+            // not to cause any permanent mayhem.
+            return false;
+        }
+
+        int newState = Integer.parseInt((String) newValue);
+        int priorState = mPm.getIntentVerificationStatusAsUser(getPackageName(),
+                getCurrentUserId());
+        if (priorState == newState) {
+            return false;
+        }
+
+        boolean success = mPm.updateIntentVerificationStatusAsUser(getPackageName(), newState,
+                getCurrentUserId());
+        if (success) {
+            // Read back the state to see if the change worked.
+            int updatedState = mPm.getIntentVerificationStatusAsUser(getPackageName(),
+                    getCurrentUserId());
+            success = (newState == updatedState);
+        } else {
+            LOG.e("Couldn't update intent verification status!");
+        }
+        return success;
+    }
+
+    private int linkStateToIndex(int state) {
+        switch (state) {
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+                return getPreference().findIndexOfValue(
+                        Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS));
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+                return getPreference().findIndexOfValue(
+                        Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER));
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+            default:
+                return getPreference().findIndexOfValue(
+                        Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/ApplicationLaunchSettingsFragment.java b/src/com/android/car/settings/applications/managedomainurls/ApplicationLaunchSettingsFragment.java
new file mode 100644
index 0000000..198d67a
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/ApplicationLaunchSettingsFragment.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Settings screen to show details about launching a specific app. */
+public class ApplicationLaunchSettingsFragment extends SettingsFragment {
+
+    @VisibleForTesting
+    static final String ARG_PACKAGE_NAME = "arg_package_name";
+
+    private ApplicationsState mState;
+    private ApplicationsState.AppEntry mAppEntry;
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    /** Creates a new instance of this fragment for the package specified in the arguments. */
+    public static ApplicationLaunchSettingsFragment newInstance(String pkg) {
+        ApplicationLaunchSettingsFragment fragment = new ApplicationLaunchSettingsFragment();
+        Bundle args = new Bundle();
+        args.putString(ARG_PACKAGE_NAME, pkg);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.application_launch_settings_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        mState = ApplicationsState.getInstance(requireActivity().getApplication());
+
+        String pkgName = getArguments().getString(ARG_PACKAGE_NAME);
+        mAppEntry = mState.getEntry(pkgName, mCarUserManagerHelper.getCurrentProcessUserId());
+
+        ApplicationWithVersionPreferenceController appController = use(
+                ApplicationWithVersionPreferenceController.class,
+                R.string.pk_opening_links_app_details);
+        appController.setAppState(mState);
+        appController.setAppEntry(mAppEntry);
+
+        List<AppLaunchSettingsBasePreferenceController> preferenceControllers = Arrays.asList(
+                use(AppLinkStatePreferenceController.class,
+                        R.string.pk_opening_links_app_details_state),
+                use(DomainUrlsPreferenceController.class,
+                        R.string.pk_opening_links_app_details_urls),
+                use(ClearDefaultsPreferenceController.class,
+                        R.string.pk_opening_links_app_details_reset));
+
+        for (AppLaunchSettingsBasePreferenceController controller : preferenceControllers) {
+            controller.setAppEntry(mAppEntry);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/ApplicationWithVersionPreferenceController.java b/src/com/android/car/settings/applications/managedomainurls/ApplicationWithVersionPreferenceController.java
new file mode 100644
index 0000000..5915c07
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/ApplicationWithVersionPreferenceController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.applications.ApplicationPreferenceController;
+import com.android.car.settings.common.FragmentController;
+
+/** In addition to showing the app name and icon, shows the app version in the summary. */
+public class ApplicationWithVersionPreferenceController extends ApplicationPreferenceController {
+
+    public ApplicationWithVersionPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        super.updateState(preference);
+        preference.setSummary(getAppVersion());
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/ClearDefaultsPreferenceController.java b/src/com/android/car/settings/applications/managedomainurls/ClearDefaultsPreferenceController.java
new file mode 100644
index 0000000..e608196
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/ClearDefaultsPreferenceController.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.usb.IUsbManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.applications.AppUtils;
+
+/**
+ * Business logic to reset a user preference for auto launching an app.
+ *
+ * <p>i.e. if a user has both NavigationAppA and NavigationAppB installed and NavigationAppA is set
+ * as the default navigation app, the user can reset that preference to pick a different default
+ * navigation app.
+ */
+public class ClearDefaultsPreferenceController extends
+        AppLaunchSettingsBasePreferenceController<Preference> {
+
+    private static final Logger LOG = new Logger(ClearDefaultsPreferenceController.class);
+
+    private final IUsbManager mUsbManager;
+    private final PackageManager mPm;
+
+    public ClearDefaultsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        IBinder b = ServiceManager.getService(Context.USB_SERVICE);
+        mUsbManager = IUsbManager.Stub.asInterface(b);
+        mPm = context.getPackageManager();
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        boolean autoLaunchEnabled = AppUtils.hasPreferredActivities(mPm, getPackageName())
+                || isDefaultBrowser(getPackageName())
+                || hasUsbDefaults(mUsbManager, getPackageName());
+
+        preference.setEnabled(autoLaunchEnabled);
+        if (autoLaunchEnabled) {
+            preference.setTitle(R.string.auto_launch_reset_text);
+            preference.setSummary(R.string.auto_launch_enable_text);
+        } else {
+            preference.setTitle(R.string.auto_launch_disable_text);
+            preference.setSummary(null);
+        }
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        if (mUsbManager != null) {
+            int userId = getCurrentUserId();
+            mPm.clearPackagePreferredActivities(getPackageName());
+            if (isDefaultBrowser(getPackageName())) {
+                mPm.setDefaultBrowserPackageNameAsUser(/* packageName= */ null, userId);
+            }
+            try {
+                mUsbManager.clearDefaults(getPackageName(), userId);
+            } catch (RemoteException e) {
+                LOG.e("mUsbManager.clearDefaults", e);
+            }
+            refreshUi();
+        }
+        return true;
+    }
+
+    private boolean isDefaultBrowser(String packageName) {
+        String defaultBrowser = mPm.getDefaultBrowserPackageNameAsUser(getCurrentUserId());
+        return packageName.equals(defaultBrowser);
+    }
+
+    private boolean hasUsbDefaults(IUsbManager usbManager, String packageName) {
+        try {
+            if (usbManager != null) {
+                return usbManager.hasDefaults(packageName, getCurrentUserId());
+            }
+        } catch (RemoteException e) {
+            LOG.e("mUsbManager.hasDefaults", e);
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/DomainAppPreferenceController.java b/src/com/android/car/settings/applications/managedomainurls/DomainAppPreferenceController.java
new file mode 100644
index 0000000..a45796c
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/DomainAppPreferenceController.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import android.app.Application;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.IconDrawableFactory;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.ArrayList;
+
+/** Business logic to populate the list of apps that deal with domain urls. */
+public class DomainAppPreferenceController extends PreferenceController<PreferenceGroup> {
+
+    private final ApplicationsState mApplicationsState;
+    private final PackageManager mPm;
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    @VisibleForTesting
+    final ApplicationsState.Callbacks mApplicationStateCallbacks =
+            new ApplicationsState.Callbacks() {
+                @Override
+                public void onRunningStateChanged(boolean running) {
+                }
+
+                @Override
+                public void onPackageListChanged() {
+                }
+
+                @Override
+                public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+                    rebuildAppList(apps);
+                }
+
+                @Override
+                public void onPackageIconChanged() {
+                }
+
+                @Override
+                public void onPackageSizeChanged(String packageName) {
+                }
+
+                @Override
+                public void onAllSizesComputed() {
+                }
+
+                @Override
+                public void onLauncherInfoChanged() {
+                }
+
+                @Override
+                public void onLoadEntriesCompleted() {
+                    mSession.rebuild(ApplicationsState.FILTER_WITH_DOMAIN_URLS,
+                            ApplicationsState.ALPHA_COMPARATOR);
+                }
+            };
+
+    private ApplicationsState.Session mSession;
+    private ArrayMap<String, Preference> mPreferenceCache;
+
+    public DomainAppPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mApplicationsState = ApplicationsState.getInstance(
+                (Application) context.getApplicationContext());
+        mPm = context.getPackageManager();
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mSession == null) {
+            throw new IllegalStateException("session should be non null by this point");
+        }
+    }
+
+    /** Sets the lifecycle to create a new session. */
+    public void setLifecycle(Lifecycle lifecycle) {
+        mSession = mApplicationsState.newSession(mApplicationStateCallbacks, lifecycle);
+    }
+
+    @Override
+    protected void onStartInternal() {
+        // Resume the session earlier than the lifecycle so that cached information is updated
+        // even if settings is not resumed (for example in multi-display).
+        mSession.onResume();
+    }
+
+    @Override
+    protected void onStopInternal() {
+        // Since we resume early in onStart, make sure we clean up even if we don't receive onPause.
+        mSession.onPause();
+    }
+
+    private void rebuildAppList(ArrayList<ApplicationsState.AppEntry> apps) {
+        PreferenceGroup preferenceGroup = getPreference();
+        preferenceGroup.removeAll();
+        for (int i = 0; i < apps.size(); i++) {
+            ApplicationsState.AppEntry entry = apps.get(i);
+            preferenceGroup.addPreference(createPreference(entry));
+        }
+    }
+
+    private Preference createPreference(ApplicationsState.AppEntry entry) {
+        String key = entry.info.packageName + "|" + entry.info.uid;
+        IconDrawableFactory iconDrawableFactory = IconDrawableFactory.newInstance(getContext());
+        Preference preference = new Preference(getContext());
+        preference.setKey(key);
+        preference.setTitle(entry.label);
+        preference.setSummary(
+                DomainUrlsUtils.getDomainsSummary(getContext(), entry.info.packageName,
+                        mCarUserManagerHelper.getCurrentProcessUserId(),
+                        DomainUrlsUtils.getHandledDomains(mPm, entry.info.packageName)));
+        preference.setIcon(iconDrawableFactory.getBadgedIcon(entry.info));
+        preference.setOnPreferenceClickListener(pref -> {
+            getFragmentController().launchFragment(
+                    ApplicationLaunchSettingsFragment.newInstance(entry.info.packageName));
+            return true;
+        });
+        return preference;
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/DomainUrlsPreferenceController.java b/src/com/android/car/settings/applications/managedomainurls/DomainUrlsPreferenceController.java
new file mode 100644
index 0000000..80b8f4e
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/DomainUrlsPreferenceController.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.util.ArraySet;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+
+/** Business logic to generate and see the list of supported domain urls. */
+public class DomainUrlsPreferenceController extends
+        AppLaunchSettingsBasePreferenceController<Preference> {
+
+    private ArraySet<String> mDomains;
+
+    public DomainUrlsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mDomains = DomainUrlsUtils.getHandledDomains(getContext().getPackageManager(),
+                getPackageName());
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setEnabled(!isBrowserApp());
+        preference.setSummary(
+                DomainUrlsUtils.getDomainsSummary(getContext(), getPackageName(),
+                        getCurrentUserId(), mDomains));
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        String newLines = System.lineSeparator() + System.lineSeparator();
+        String message = String.join(newLines, mDomains);
+
+        // Not exactly a "confirmation" dialog, but reusing to remove the need for a custom
+        // dialog fragment.
+        ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment.Builder(
+                getContext())
+                .setTitle(R.string.app_launch_supported_domain_urls_title)
+                .setMessage(message)
+                .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null)
+                .build();
+        getFragmentController().showDialog(dialogFragment, ConfirmationDialogFragment.TAG);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/DomainUrlsUtils.java b/src/com/android/car/settings/applications/managedomainurls/DomainUrlsUtils.java
new file mode 100644
index 0000000..9769196
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/DomainUrlsUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
+import android.util.ArraySet;
+
+import com.android.car.settings.R;
+
+import java.util.List;
+
+/** Utility functions related to handling application domain urls. */
+public final class DomainUrlsUtils {
+    private DomainUrlsUtils() {
+    }
+
+    /** Get a summary text based on the number of handled domains. */
+    public static CharSequence getDomainsSummary(Context context, String packageName, int userId,
+            ArraySet<String> domains) {
+        PackageManager pm = context.getPackageManager();
+
+        // If the user has explicitly said "no" for this package, that's the string we should show.
+        int domainStatus = pm.getIntentVerificationStatusAsUser(packageName, userId);
+        if (domainStatus == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+            return context.getText(R.string.domain_urls_summary_none);
+        }
+        // Otherwise, ask package manager for the domains for this package, and show the first
+        // one (or none if there aren't any).
+        if (domains.isEmpty()) {
+            return context.getText(R.string.domain_urls_summary_none);
+        } else if (domains.size() == 1) {
+            return context.getString(R.string.domain_urls_summary_one, domains.valueAt(0));
+        } else {
+            return context.getString(R.string.domain_urls_summary_some, domains.valueAt(0));
+        }
+    }
+
+    /** Get the list of domains handled by the given package. */
+    public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) {
+        List<IntentFilterVerificationInfo> iviList = pm.getIntentFilterVerifications(packageName);
+        List<IntentFilter> filters = pm.getAllIntentFilters(packageName);
+
+        ArraySet<String> result = new ArraySet<>();
+        if (iviList != null && iviList.size() > 0) {
+            for (IntentFilterVerificationInfo ivi : iviList) {
+                for (String host : ivi.getDomains()) {
+                    result.add(host);
+                }
+            }
+        }
+        if (filters != null && filters.size() > 0) {
+            for (IntentFilter filter : filters) {
+                if (filter.hasCategory(Intent.CATEGORY_BROWSABLE)
+                        && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP)
+                        || filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) {
+                    result.addAll(filter.getHostsList());
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/car/settings/applications/managedomainurls/ManageDomainUrlsFragment.java b/src/com/android/car/settings/applications/managedomainurls/ManageDomainUrlsFragment.java
new file mode 100644
index 0000000..7aacc16
--- /dev/null
+++ b/src/com/android/car/settings/applications/managedomainurls/ManageDomainUrlsFragment.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import android.content.Context;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Fragment which shows a list of applications to manage handled domain urls. */
+public class ManageDomainUrlsFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.manage_domain_urls_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        use(DomainAppPreferenceController.class, R.string.pk_opening_links_options).setLifecycle(
+                getLifecycle());
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/AppEntryListManager.java b/src/com/android/car/settings/applications/specialaccess/AppEntryListManager.java
new file mode 100644
index 0000000..7359f80
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/AppEntryListManager.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Manages a list of {@link ApplicationsState.AppEntry} instances by syncing in the background and
+ * providing updates via a {@link Callback}. Clients may provide an {@link ExtraInfoBridge} to
+ * populate the {@link ApplicationsState.AppEntry#extraInfo} field with use case sepecific data.
+ * Clients may also provide an {@link ApplicationsState.AppFilter} via an {@link AppFilterProvider}
+ * to determine which entries will appear in the list updates.
+ *
+ * <p>Clients should call {@link #init(ExtraInfoBridge, AppFilterProvider, Callback)} to specify
+ * behavior and then {@link #start()} to begin loading. {@link #stop()} will cancel loading, and
+ * {@link #destroy()} will clean up resources when this class will no longer be used.
+ */
+public class AppEntryListManager {
+
+    /** Callback for receiving events from {@link AppEntryListManager}. */
+    public interface Callback {
+        /**
+         * Called when the list of {@link ApplicationsState.AppEntry} instances or the {@link
+         * ApplicationsState.AppEntry#extraInfo} fields have changed.
+         */
+        void onAppEntryListChanged(List<ApplicationsState.AppEntry> entries);
+    }
+
+    /**
+     * Provides an {@link ApplicationsState.AppFilter} to tailor the entries in the list updates.
+     */
+    public interface AppFilterProvider {
+        /**
+         * Returns the filter that should be used to trim the entries list before callback delivery.
+         */
+        ApplicationsState.AppFilter getAppFilter();
+    }
+
+    /** Bridges extra information to {@link ApplicationsState.AppEntry#extraInfo}. */
+    public interface ExtraInfoBridge {
+        /**
+         * Populates the {@link ApplicationsState.AppEntry#extraInfo} field on the {@code enrties}
+         * with the relevant data for the implementation.
+         */
+        void loadExtraInfo(List<ApplicationsState.AppEntry> entries);
+    }
+
+    private final ApplicationsState.Callbacks mSessionCallbacks =
+            new ApplicationsState.Callbacks() {
+                @Override
+                public void onRunningStateChanged(boolean running) {
+                    // No op.
+                }
+
+                @Override
+                public void onPackageListChanged() {
+                    forceUpdate();
+                }
+
+                @Override
+                public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+                    if (mCallback != null) {
+                        mCallback.onAppEntryListChanged(apps);
+                    }
+                }
+
+                @Override
+                public void onPackageIconChanged() {
+                    // No op.
+                }
+
+                @Override
+                public void onPackageSizeChanged(String packageName) {
+                    // No op.
+                }
+
+                @Override
+                public void onAllSizesComputed() {
+                    // No op.
+                }
+
+                @Override
+                public void onLauncherInfoChanged() {
+                    // No op.
+                }
+
+                @Override
+                public void onLoadEntriesCompleted() {
+                    mHasReceivedLoadEntries = true;
+                    forceUpdate();
+                }
+            };
+
+    private final ApplicationsState mApplicationsState;
+    private final BackgroundHandler mBackgroundHandler;
+    private final MainHandler mMainHandler;
+
+    private ExtraInfoBridge mExtraInfoBridge;
+    private AppFilterProvider mFilterProvider;
+    private Callback mCallback;
+    private ApplicationsState.Session mSession;
+
+    private boolean mHasReceivedLoadEntries;
+    private boolean mHasReceivedExtraInfo;
+
+    public AppEntryListManager(Context context) {
+        mApplicationsState = ApplicationsState.getInstance(
+                (Application) context.getApplicationContext());
+        // Run on the same background thread as the ApplicationsState to make sure updates don't
+        // conflict.
+        mBackgroundHandler = new BackgroundHandler(new WeakReference<>(this),
+                mApplicationsState.getBackgroundLooper());
+        mMainHandler = new MainHandler(new WeakReference<>(this));
+    }
+
+    /**
+     * Specifies the behavior of this manager.
+     *
+     * @param extraInfoBridge an optional bridge to load information into the entries.
+     * @param filterProvider  provides a filter to tailor the contents of the list updates.
+     * @param callback        callback to which updated lists are delivered.
+     */
+    public void init(@Nullable ExtraInfoBridge extraInfoBridge,
+            @Nullable AppFilterProvider filterProvider,
+            Callback callback) {
+        if (mSession != null) {
+            destroy();
+        }
+        mExtraInfoBridge = extraInfoBridge;
+        mFilterProvider = filterProvider;
+        mCallback = callback;
+        mSession = mApplicationsState.newSession(mSessionCallbacks);
+    }
+
+    /**
+     * Starts loading the information in the background. When loading is finished, the {@link
+     * Callback} will be notified on the main thread.
+     */
+    public void start() {
+        mSession.onResume();
+    }
+
+    /**
+     * Stops any pending loading.
+     */
+    public void stop() {
+        mSession.onPause();
+        clearHandlers();
+    }
+
+    /**
+     * Cleans up internal state when this will no longer be used.
+     */
+    public void destroy() {
+        mSession.onDestroy();
+        clearHandlers();
+        mExtraInfoBridge = null;
+        mFilterProvider = null;
+        mCallback = null;
+    }
+
+    /**
+     * Schedules updates for all {@link ApplicationsState.AppEntry} instances. When loading is
+     * finished, the {@link Callback} will be notified on the main thread.
+     */
+    public void forceUpdate() {
+        mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL);
+    }
+
+    /**
+     * Schedules an update for the given {@code entry}. When loading is finished, the {@link
+     * Callback} will be notified on the main thread.
+     */
+    public void forceUpdate(ApplicationsState.AppEntry entry) {
+        mBackgroundHandler.obtainMessage(BackgroundHandler.MSG_LOAD_PKG,
+                entry).sendToTarget();
+    }
+
+    private void rebuild() {
+        if (!mHasReceivedLoadEntries || !mHasReceivedExtraInfo) {
+            // Don't rebuild the list until all the app entries are loaded.
+            return;
+        }
+        mSession.rebuild((mFilterProvider != null) ? mFilterProvider.getAppFilter()
+                        : ApplicationsState.FILTER_EVERYTHING,
+                ApplicationsState.ALPHA_COMPARATOR, /* foreground= */ false);
+    }
+
+    private void clearHandlers() {
+        mBackgroundHandler.removeMessages(BackgroundHandler.MSG_LOAD_ALL);
+        mBackgroundHandler.removeMessages(BackgroundHandler.MSG_LOAD_PKG);
+        mMainHandler.removeMessages(MainHandler.MSG_INFO_UPDATED);
+    }
+
+    private void loadInfo(List<ApplicationsState.AppEntry> entries) {
+        if (mExtraInfoBridge != null) {
+            mExtraInfoBridge.loadExtraInfo(entries);
+        }
+        for (ApplicationsState.AppEntry entry : entries) {
+            mApplicationsState.ensureIcon(entry);
+        }
+    }
+
+    private static class BackgroundHandler extends Handler {
+        private static final int MSG_LOAD_ALL = 1;
+        private static final int MSG_LOAD_PKG = 2;
+
+        private final WeakReference<AppEntryListManager> mOuter;
+
+        BackgroundHandler(WeakReference<AppEntryListManager> outer, Looper looper) {
+            super(looper);
+            mOuter = outer;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            AppEntryListManager outer = mOuter.get();
+            if (outer == null) {
+                return;
+            }
+            switch (msg.what) {
+                case MSG_LOAD_ALL:
+                    outer.loadInfo(outer.mSession.getAllApps());
+                    outer.mMainHandler.sendEmptyMessage(MainHandler.MSG_INFO_UPDATED);
+                    break;
+                case MSG_LOAD_PKG:
+                    ApplicationsState.AppEntry entry = (ApplicationsState.AppEntry) msg.obj;
+                    outer.loadInfo(Collections.singletonList(entry));
+                    outer.mMainHandler.sendEmptyMessage(MainHandler.MSG_INFO_UPDATED);
+                    break;
+            }
+        }
+    }
+
+    private static class MainHandler extends Handler {
+        private static final int MSG_INFO_UPDATED = 1;
+
+        private final WeakReference<AppEntryListManager> mOuter;
+
+        MainHandler(WeakReference<AppEntryListManager> outer) {
+            mOuter = outer;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            AppEntryListManager outer = mOuter.get();
+            if (outer == null) {
+                return;
+            }
+            switch (msg.what) {
+                case MSG_INFO_UPDATED:
+                    outer.mHasReceivedExtraInfo = true;
+                    outer.rebuild();
+                    break;
+            }
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/AppOpsFragment.java b/src/com/android/car/settings/applications/specialaccess/AppOpsFragment.java
new file mode 100644
index 0000000..165dbb8
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/AppOpsFragment.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.os.Bundle;
+import android.widget.Button;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Fragment which hosts an {@link AppOpsPreferenceController} to display a list of controls to
+ * allow/disallow app operations. There is a toggle in the app bar for showing/hiding system
+ * applications. The semantics of what constitues a system app is left up to the controller.
+ */
+public abstract class AppOpsFragment extends SettingsFragment {
+
+    private static final String KEY_SHOW_SYSTEM = "showSystem";
+
+    private boolean mShowSystem;
+
+    @Override
+    @XmlRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    /** Returns the {@link AppOpsPreferenceController} via {@link #use(Class, int)} lookup. */
+    protected abstract AppOpsPreferenceController lookupAppOpsPreferenceController();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mShowSystem = savedInstanceState.getBoolean(KEY_SHOW_SYSTEM, false);
+            lookupAppOpsPreferenceController().setShowSystem(mShowSystem);
+        }
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        Button toggleShowSystem = requireActivity().findViewById(R.id.action_button1);
+        setButtonText(toggleShowSystem);
+        toggleShowSystem.setOnClickListener(v -> {
+            mShowSystem = !mShowSystem;
+            lookupAppOpsPreferenceController().setShowSystem(mShowSystem);
+            setButtonText(toggleShowSystem);
+        });
+    }
+
+    private void setButtonText(Button button) {
+        // Show text to reverse the current state.
+        button.setText(mShowSystem ? R.string.hide_system : R.string.show_system);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_SHOW_SYSTEM, mShowSystem);
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceController.java
new file mode 100644
index 0000000..043aec9
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceController.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.app.AppOpsManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.applications.specialaccess.AppStateAppOpsBridge.PermissionState;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
+
+import java.util.List;
+
+/**
+ * Displays a list of toggles for applications requesting permission to perform the operation with
+ * which this controller was initialized. {@link #init(int, String, int)} should be called when
+ * this controller is instantiated to specify the {@link AppOpsManager} operation code to control
+ * access for.
+ */
+public class AppOpsPreferenceController extends PreferenceController<PreferenceGroup> {
+
+    private static final AppFilter FILTER_HAS_INFO = new AppFilter() {
+        @Override
+        public void init() {
+            // No op.
+        }
+
+        @Override
+        public boolean filterApp(AppEntry info) {
+            return info.extraInfo != null;
+        }
+    };
+
+    private final AppOpsManager mAppOpsManager;
+
+    private final Preference.OnPreferenceChangeListener mOnPreferenceChangeListener =
+            new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newValue) {
+                    AppOpPreference appOpPreference = (AppOpPreference) preference;
+                    AppEntry entry = appOpPreference.mEntry;
+                    PermissionState extraInfo = (PermissionState) entry.extraInfo;
+                    boolean allowOp = (Boolean) newValue;
+                    if (allowOp != extraInfo.isPermissible()) {
+                        mAppOpsManager.setMode(mAppOpsOpCode, entry.info.uid,
+                                entry.info.packageName,
+                                allowOp ? AppOpsManager.MODE_ALLOWED : mNegativeOpMode);
+                        // Update the extra info of this entry so that it reflects the new mode.
+                        mAppEntryListManager.forceUpdate(entry);
+                        return true;
+                    }
+                    return false;
+                }
+            };
+
+    private final AppEntryListManager.Callback mCallback = new AppEntryListManager.Callback() {
+        @Override
+        public void onAppEntryListChanged(List<AppEntry> entries) {
+            mEntries = entries;
+            refreshUi();
+        }
+    };
+
+    private int mAppOpsOpCode = AppOpsManager.OP_NONE;
+    private String mPermission;
+    private int mNegativeOpMode = -1;
+
+    @VisibleForTesting
+    AppEntryListManager mAppEntryListManager;
+    private List<AppEntry> mEntries;
+
+    private boolean mShowSystem;
+
+    public AppOpsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        mAppEntryListManager = new AppEntryListManager(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /**
+     * Initializes this controller with the {@code appOpsOpCode} (selected from the operations in
+     * {@link AppOpsManager}) to control access for.
+     *
+     * @param permission     the {@link android.Manifest.permission} apps must hold to perform the
+     *                       operation.
+     * @param negativeOpMode the operation mode that will be passed to {@link
+     *                       AppOpsManager#setMode(int, int, String, int)} when access for a app is
+     *                       revoked.
+     */
+    public void init(int appOpsOpCode, String permission, int negativeOpMode) {
+        mAppOpsOpCode = appOpsOpCode;
+        mPermission = permission;
+        mNegativeOpMode = negativeOpMode;
+    }
+
+    /**
+     * Rebuilds the preference list to show system applications if {@code showSystem} is true.
+     * System applications will be hidden otherwise.
+     */
+    public void setShowSystem(boolean showSystem) {
+        if (mShowSystem != showSystem) {
+            mShowSystem = showSystem;
+            mAppEntryListManager.forceUpdate();
+        }
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mAppOpsOpCode == AppOpsManager.OP_NONE) {
+            throw new IllegalStateException("App operation code must be initialized");
+        }
+        if (mPermission == null) {
+            throw new IllegalStateException("Manifest permission must be initialized");
+        }
+        if (mNegativeOpMode == -1) {
+            throw new IllegalStateException("Negative case app operation mode must be initialized");
+        }
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        AppStateAppOpsBridge extraInfoBridge = new AppStateAppOpsBridge(getContext(), mAppOpsOpCode,
+                mPermission);
+        mAppEntryListManager.init(extraInfoBridge, this::getAppFilter, mCallback);
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mAppEntryListManager.start();
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mAppEntryListManager.stop();
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mAppEntryListManager.destroy();
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        if (mEntries == null) {
+            // Still loading.
+            return;
+        }
+        preference.removeAll();
+        for (AppEntry entry : mEntries) {
+            Preference appOpPreference = new AppOpPreference(getContext(), entry);
+            appOpPreference.setOnPreferenceChangeListener(mOnPreferenceChangeListener);
+            preference.addPreference(appOpPreference);
+        }
+    }
+
+    @CallSuper
+    protected AppFilter getAppFilter() {
+        AppFilter filterObj = new CompoundFilter(FILTER_HAS_INFO,
+                ApplicationsState.FILTER_NOT_HIDE);
+        if (!mShowSystem) {
+            filterObj = new CompoundFilter(filterObj,
+                    ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER);
+        }
+        return filterObj;
+    }
+
+    private static class AppOpPreference extends SwitchPreference {
+
+        private final AppEntry mEntry;
+
+        AppOpPreference(Context context, AppEntry entry) {
+            super(context);
+            String key = entry.info.packageName + "|" + entry.info.uid;
+            setKey(key);
+            setTitle(entry.label);
+            setIcon(entry.icon);
+            setSummary(getAppStateText(entry.info));
+            setPersistent(false);
+            PermissionState extraInfo = (PermissionState) entry.extraInfo;
+            setChecked(extraInfo.isPermissible());
+            mEntry = entry;
+        }
+
+        private String getAppStateText(ApplicationInfo info) {
+            if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+                return getContext().getString(R.string.not_installed);
+            } else if (!info.enabled || info.enabledSetting
+                    == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                return getContext().getString(R.string.disabled);
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/AppStateAppOpsBridge.java b/src/com/android/car/settings/applications/specialaccess/AppStateAppOpsBridge.java
new file mode 100644
index 0000000..4a3d0a1
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/AppStateAppOpsBridge.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.common.Logger;
+import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Bridges {@link AppOpsManager} app operation permission information into {@link
+ * AppEntry#extraInfo} as {@link PermissionState} objects.
+ */
+public class AppStateAppOpsBridge implements AppEntryListManager.ExtraInfoBridge {
+
+    private static final Logger LOG = new Logger(AppStateAppOpsBridge.class);
+
+    private final Context mContext;
+    private final IPackageManager mIPackageManager;
+    private final List<UserHandle> mProfiles;
+    private final AppOpsManager mAppOpsManager;
+    private final int mAppOpsOpCode;
+    private final String mPermission;
+
+    /**
+     * Constructor.
+     *
+     * @param appOpsOpCode the {@link AppOpsManager} op code constant to fetch information for.
+     * @param permission   the {@link android.Manifest.permission} required to perform the
+     *                     operation.
+     */
+    public AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission) {
+        this(context, appOpsOpCode, permission, AppGlobals.getPackageManager());
+    }
+
+    @VisibleForTesting
+    AppStateAppOpsBridge(Context context, int appOpsOpCode, String permission,
+            IPackageManager packageManager) {
+        mContext = context;
+        mIPackageManager = packageManager;
+        mProfiles = UserManager.get(context).getUserProfiles();
+        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        mAppOpsOpCode = appOpsOpCode;
+        mPermission = permission;
+    }
+
+    @Override
+    public void loadExtraInfo(List<AppEntry> entries) {
+        SparseArray<Map<String, PermissionState>> packageToStatesMapByProfileId =
+                getPackageToStateMapsByProfileId();
+        loadAppOpModes(packageToStatesMapByProfileId);
+
+        for (AppEntry entry : entries) {
+            Map<String, PermissionState> packageStatesMap = packageToStatesMapByProfileId.get(
+                    UserHandle.getUserId(entry.info.uid));
+            entry.extraInfo = (packageStatesMap != null) ? packageStatesMap.get(
+                    entry.info.packageName) : null;
+        }
+    }
+
+    private SparseArray<Map<String, PermissionState>> getPackageToStateMapsByProfileId() {
+        SparseArray<Map<String, PermissionState>> entries = new SparseArray<>();
+        try {
+            for (UserHandle profile : mProfiles) {
+                int profileId = profile.getIdentifier();
+                List<PackageInfo> packageInfos = getPackageInfos(profileId);
+                Map<String, PermissionState> entriesForProfile = new ArrayMap<>();
+                entries.put(profileId, entriesForProfile);
+                for (PackageInfo packageInfo : packageInfos) {
+                    boolean isAvailable = mIPackageManager.isPackageAvailable(
+                            packageInfo.packageName,
+                            profileId);
+                    if (shouldIgnorePackage(packageInfo) || !isAvailable) {
+                        LOG.d("Ignoring " + packageInfo.packageName + " isAvailable="
+                                + isAvailable);
+                        continue;
+                    }
+                    PermissionState newEntry = new PermissionState();
+                    newEntry.mRequestedPermissions = packageInfo.requestedPermissions;
+                    entriesForProfile.put(packageInfo.packageName, newEntry);
+                }
+            }
+        } catch (RemoteException e) {
+            LOG.w("PackageManager is dead. Can't get list of packages requesting "
+                    + mPermission, e);
+        }
+        return entries;
+    }
+
+    @SuppressWarnings("unchecked") // safe by specification.
+    private List<PackageInfo> getPackageInfos(int profileId) throws RemoteException {
+        return mIPackageManager.getPackagesHoldingPermissions(new String[]{mPermission},
+                PackageManager.GET_PERMISSIONS, profileId).getList();
+    }
+
+    private boolean shouldIgnorePackage(PackageInfo packageInfo) {
+        return packageInfo.packageName.equals("android")
+                || packageInfo.packageName.equals(mContext.getPackageName())
+                || !ArrayUtils.contains(packageInfo.requestedPermissions, mPermission);
+    }
+
+    /** Sets the {@link PermissionState#mAppOpMode} field. */
+    private void loadAppOpModes(
+            SparseArray<Map<String, PermissionState>> packageToStateMapsByProfileId) {
+        // Find out which packages have been granted permission from AppOps.
+        List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
+                new int[]{mAppOpsOpCode});
+        if (packageOps == null) {
+            return;
+        }
+        for (AppOpsManager.PackageOps packageOp : packageOps) {
+            int userId = UserHandle.getUserId(packageOp.getUid());
+            Map<String, PermissionState> packageStateMap = packageToStateMapsByProfileId.get(
+                    userId);
+            if (packageStateMap == null) {
+                // Profile is not for the current user.
+                continue;
+            }
+            PermissionState permissionState = packageStateMap.get(packageOp.getPackageName());
+            if (permissionState == null) {
+                LOG.w("AppOp permission exists for package " + packageOp.getPackageName()
+                        + " of user " + userId + " but package doesn't exist or did not request "
+                        + mPermission + " access");
+                continue;
+            }
+            if (packageOp.getOps().size() < 1) {
+                LOG.w("No AppOps permission exists for package " + packageOp.getPackageName());
+                continue;
+            }
+            permissionState.mAppOpMode = packageOp.getOps().get(0).getMode();
+        }
+    }
+
+    /**
+     * Data class for use in {@link AppEntry#extraInfo} which indicates whether
+     * the app operation used to construct the data bridge is permitted for the associated
+     * application.
+     */
+    public static class PermissionState {
+        private String[] mRequestedPermissions;
+        private int mAppOpMode = AppOpsManager.MODE_DEFAULT;
+
+        /** Returns {@code true} if the entry's application is allowed to perform the operation. */
+        public boolean isPermissible() {
+            // Default behavior is permissible as long as the package requested this permission.
+            if (mAppOpMode == AppOpsManager.MODE_DEFAULT) {
+                return true;
+            }
+            return mAppOpMode == AppOpsManager.MODE_ALLOWED;
+        }
+
+        /** Returns the permissions requested by the entry's application. */
+        public String[] getRequestedPermissions() {
+            return mRequestedPermissions;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridge.java b/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridge.java
new file mode 100644
index 0000000..b13fedb
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridge.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.os.RemoteException;
+
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.List;
+
+/**
+ * Bridges the value of {@link ISms#getPremiumSmsPermission(String)} into the {@link
+ * ApplicationsState.AppEntry#extraInfo} for each entry's package name.
+ */
+public class AppStatePremiumSmsBridge implements AppEntryListManager.ExtraInfoBridge {
+
+    private final ISms mSmsManager;
+
+    public AppStatePremiumSmsBridge(ISms smsManager) {
+        mSmsManager = smsManager;
+    }
+
+    @Override
+    public void loadExtraInfo(List<ApplicationsState.AppEntry> entries) {
+        for (ApplicationsState.AppEntry entry : entries) {
+            entry.extraInfo = getSmsState(entry.info.packageName);
+        }
+    }
+
+    private int getSmsState(String packageName) {
+        try {
+            return mSmsManager.getPremiumSmsPermission(packageName);
+        } catch (RemoteException e) {
+            return SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsFragment.java b/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsFragment.java
new file mode 100644
index 0000000..ac9d1d8
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsFragment.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.applications.ApplicationPreferenceController;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.ArrayList;
+
+/** Displays directory access permissions for a specific package. */
+public class DirectoryAccessDetailsFragment extends SettingsFragment {
+
+    public static final String ARG_PACKAGE_NAME = "package";
+
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private String mPackageName;
+    private ApplicationsState mAppState;
+    private ApplicationsState.Session mSession;
+    private ApplicationsState.AppEntry mAppEntry;
+
+    /** Creates an instance of this fragment, passing {@code packageName} as an argument. */
+    public static DirectoryAccessDetailsFragment getInstance(String packageName) {
+        DirectoryAccessDetailsFragment directoryAccessDetailsFragment =
+                new DirectoryAccessDetailsFragment();
+        Bundle bundle = new Bundle();
+        bundle.putString(ARG_PACKAGE_NAME, packageName);
+        directoryAccessDetailsFragment.setArguments(bundle);
+        return directoryAccessDetailsFragment;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.directory_access_details_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+
+        mPackageName = getArguments().getString(ARG_PACKAGE_NAME);
+
+        mAppState = ApplicationsState.getInstance(requireActivity().getApplication());
+        mSession = mAppState.newSession(mApplicationStateCallbacks, getLifecycle());
+
+        retrieveAppEntry();
+
+        use(ApplicationPreferenceController.class,
+                R.string.pk_directory_access_details_app).setAppEntry(mAppEntry).setAppState(
+                mAppState);
+        use(DirectoryAccessDetailsPreferenceController.class,
+                R.string.pk_directory_access_details).setPackage(mPackageName);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        // Resume the session earlier than the lifecycle so that cached information is updated
+        // even if settings is not resumed (for example in multi-display).
+        mSession.onResume();
+        refresh();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        // Since we resume early in onStart, make sure we clean up even if we don't receive onPause.
+        mSession.onPause();
+    }
+
+    private void refresh() {
+        retrieveAppEntry();
+        if (mAppEntry == null) {
+            goBack();
+        }
+    }
+
+    private void retrieveAppEntry() {
+        mAppEntry = mAppState.getEntry(mPackageName,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+    }
+
+    private final ApplicationsState.Callbacks mApplicationStateCallbacks =
+            new ApplicationsState.Callbacks() {
+                @Override
+                public void onRunningStateChanged(boolean running) {
+                }
+
+                @Override
+                public void onPackageListChanged() {
+                    refresh();
+                }
+
+                @Override
+                public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+                }
+
+                @Override
+                public void onPackageIconChanged() {
+                }
+
+                @Override
+                public void onPackageSizeChanged(String packageName) {
+                }
+
+                @Override
+                public void onAllSizesComputed() {
+                }
+
+                @Override
+                public void onLauncherInfoChanged() {
+                }
+
+                @Override
+                public void onLoadEntriesCompleted() {
+                }
+            };
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsPreferenceController.java
new file mode 100644
index 0000000..e565e91
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsPreferenceController.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.AUTHORITY;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.COL_GRANTED;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COLUMNS;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_DIRECTORY;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_GRANTED;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_PACKAGE;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_VOLUME_UUID;
+
+import android.annotation.Nullable;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Detailed settings for an app's directory access permissions (A.K.A Scoped Directory Access).
+ *
+ * <p>It shows the directories for which the user denied access with the "Do not ask again" flag.
+ * The user can use the preference toggles to grant access again.
+ *
+ * <p>This controller dynamically lists all such permissions starting with one preference per
+ * directory in the primary storage then adding additional preferences for external volumes (one
+ * for the whole volume and one for each individual directory). Granting access to a whole volume
+ * will hide individual directory permissions.
+ */
+public class DirectoryAccessDetailsPreferenceController extends
+        PreferenceController<PreferenceGroup> {
+
+    private static final Logger LOG = new Logger(DirectoryAccessDetailsPreferenceController.class);
+
+    private String mPackageName;
+
+    public DirectoryAccessDetailsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /**
+     * Sets the package for which to display directory access. This should be called right after the
+     * controller is instantiated.
+     */
+    public void setPackage(String packageName) {
+        mPackageName = packageName;
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mPackageName == null) {
+            throw new IllegalStateException("Must specify package for directory access details");
+        }
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        preferenceGroup.removeAll();
+        preferenceGroup.setOrderingAsAdded(false);
+
+        Map<String, ExternalVolume> externalVolumes = new ArrayMap<>();
+        Uri providerUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(
+                AUTHORITY).appendPath(TABLE_PERMISSIONS).appendPath("*").build();
+        // Query provider for entries.
+        try (Cursor cursor = getContext().getContentResolver().query(providerUri,
+                TABLE_PERMISSIONS_COLUMNS, /* selection= */ null,
+                new String[]{mPackageName}, /* sortOrder= */ null)) {
+            if (cursor == null) {
+                LOG.w("Didn't get cursor for " + mPackageName);
+                return;
+            }
+            int count = cursor.getCount();
+            if (count == 0) {
+                // This setting screen should not be reached if there was no permission, so just
+                // ignore it.
+                LOG.w("No permissions for " + mPackageName);
+                return;
+            }
+
+            while (cursor.moveToNext()) {
+                String pkg = cursor.getString(TABLE_PERMISSIONS_COL_PACKAGE);
+                String uuid = cursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID);
+                String dir = cursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY);
+                boolean granted = cursor.getInt(TABLE_PERMISSIONS_COL_GRANTED) == 1;
+                LOG.v("Pkg:" + pkg + " uuid: " + uuid + " dir: " + dir + " granted:" + granted);
+
+                if (!mPackageName.equals(pkg)) {
+                    // Sanity check, shouldn't happen.
+                    LOG.w("Ignoring " + uuid + "/" + dir + " due to package mismatch: "
+                            + "expected " + mPackageName + ", got " + pkg);
+                    continue;
+                }
+
+                if (uuid == null) {
+                    if (dir == null) {
+                        // Sanity check, shouldn't happen.
+                        LOG.wtf("Ignoring permission on primary storage root");
+                    } else {
+                        // Primary storage entry: add right away
+                        preferenceGroup.addPreference(
+                                createPreference(dir, providerUri, /* uuid= */ null, dir,
+                                        granted));
+                    }
+                } else {
+                    // External volume entry: save it for later.
+                    ExternalVolume externalVolume = externalVolumes.get(uuid);
+                    if (externalVolume == null) {
+                        externalVolume = new ExternalVolume(uuid);
+                        externalVolumes.put(uuid, externalVolume);
+                    }
+                    if (dir == null) {
+                        // Whole volume.
+                        externalVolume.mIsGranted = granted;
+                    } else {
+                        // Directory only.
+                        externalVolume.mChildren.add(new Pair<>(dir, granted));
+                    }
+                }
+            }
+        }
+
+        LOG.v("external volumes: " + externalVolumes);
+
+        if (externalVolumes.isEmpty()) {
+            // We're done!
+            return;
+        }
+
+        // Add entries from external volumes
+
+        // Query StorageManager to get the user-friendly volume names.
+        StorageManager sm = getContext().getSystemService(StorageManager.class);
+        List<VolumeInfo> volumes = sm.getVolumes();
+        if (volumes.isEmpty()) {
+            LOG.w("StorageManager returned no secondary volumes");
+            return;
+        }
+        Map<String, String> volumeNames = new HashMap<>(volumes.size());
+        for (VolumeInfo volume : volumes) {
+            String uuid = volume.getFsUuid();
+            if (uuid == null) {
+                continue; // Primary storage, only directory name used.
+            }
+            String name = sm.getBestVolumeDescription(volume);
+            if (name == null) {
+                LOG.w("No description for " + volume + "; using uuid instead: " + uuid);
+                name = uuid;
+            }
+            volumeNames.put(uuid, name);
+        }
+        LOG.v("UUID -> name mapping: " + volumeNames);
+
+        for (ExternalVolume volume : externalVolumes.values()) {
+            String volumeName = volumeNames.get(volume.mUuid);
+            if (volumeName == null) {
+                LOG.w("Ignoring entry for invalid UUID: " + volume.mUuid);
+                continue;
+            }
+            // First add the preference for the whole volume...
+            preferenceGroup.addPreference(createPreference(volumeName, providerUri, volume.mUuid,
+                    /* dir= */ null, volume.mIsGranted));
+
+            // ... then the child preferences for directories.
+            if (!volume.mIsGranted) {
+                volume.mChildren.forEach(pair -> {
+                    String dir = pair.first;
+                    boolean isGranted = pair.second;
+                    String name = getContext().getResources()
+                            .getString(R.string.directory_on_volume, volumeName, dir);
+                    SwitchPreference childPref =
+                            createPreference(name, providerUri, volume.mUuid, dir, isGranted);
+                    preferenceGroup.addPreference(childPref);
+                });
+            }
+        }
+    }
+
+    private SwitchPreference createPreference(String title, Uri providerUri, String uuid,
+            String dir, boolean isGranted) {
+        SwitchPreference pref = new SwitchPreference(getContext());
+        pref.setKey(String.format("%s:%s", uuid, dir));
+        pref.setTitle(title);
+        pref.setChecked(isGranted);
+        pref.setPersistent(false);
+        pref.setOnPreferenceChangeListener((unused, value) -> {
+            boolean newGrantedState = (Boolean) value;
+            setGranted(newGrantedState, providerUri, uuid, dir);
+            refreshUi();
+            return true;
+        });
+        return pref;
+    }
+
+    private void setGranted(boolean isGranted, Uri providerUri,
+            @Nullable String uuid, @Nullable String directory) {
+        LOG.d("Asking " + providerUri + " to update " + uuid + "/" + directory + " to "
+                + isGranted);
+        ContentValues values = new ContentValues(1);
+        values.put(COL_GRANTED, isGranted);
+        int updated = getContext().getContentResolver().update(providerUri, values,
+                /* where= */ null, new String[]{mPackageName, uuid, directory});
+        LOG.d("Updated " + updated + " entries for " + uuid + "/" + directory);
+    }
+
+    private static class ExternalVolume {
+
+        String mUuid;
+        /** Key: directory, Value: isGranted */
+        List<Pair<String, Boolean>> mChildren = new ArrayList<>();
+        boolean mIsGranted;
+
+        ExternalVolume(String uuid) {
+            mUuid = uuid;
+        }
+
+        @Override
+        public String toString() {
+            return "ExternalVolume: [uuid=" + mUuid + ", granted=" + mIsGranted + ", children="
+                    + mChildren + "]";
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/DirectoryAccessFragment.java b/src/com/android/car/settings/applications/specialaccess/DirectoryAccessFragment.java
new file mode 100644
index 0000000..cb6394b
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/DirectoryAccessFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Displays the list of applications which have been denied external storage directory access. */
+public class DirectoryAccessFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.directory_access_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/DirectoryAccessPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/DirectoryAccessPreferenceController.java
new file mode 100644
index 0000000..60c9a22
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/DirectoryAccessPreferenceController.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.AUTHORITY;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COLUMNS;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COL_PACKAGE;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.ArraySet;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Displays a list of preferences for apps that have directory access permissions set. Selecting an
+ * app launches a detailed view for controlling permissions at the directory level.
+ */
+public class DirectoryAccessPreferenceController extends PreferenceController<PreferenceGroup> {
+
+    private static final Logger LOG = new Logger(DirectoryAccessPreferenceController.class);
+
+    private static final AppFilter FILTER_APP_HAS_DIRECTORY_ACCESS = new AppFilter() {
+
+        private Set<String> mPackages;
+
+        @Override
+        public void init() {
+            throw new UnsupportedOperationException("Need to call constructor that takes context");
+        }
+
+        @Override
+        public void init(Context context) {
+            mPackages = null;
+            Uri providerUri = new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority(AUTHORITY)
+                    .appendPath(TABLE_PACKAGES)
+                    .appendPath("*")
+                    .build();
+            try (Cursor cursor = context.getContentResolver().query(providerUri,
+                    TABLE_PACKAGES_COLUMNS, /* queryArgs= */ null, /* cancellationSignal= */
+                    null)) {
+                if (cursor == null) {
+                    LOG.w("Didn't get cursor for " + providerUri);
+                    return;
+                }
+                int count = cursor.getCount();
+                if (count == 0) {
+                    LOG.d("No packages anymore (was " + mPackages + ")");
+                    return;
+                }
+                mPackages = new ArraySet<>(count);
+                while (cursor.moveToNext()) {
+                    mPackages.add(cursor.getString(TABLE_PACKAGES_COL_PACKAGE));
+                }
+                LOG.d("init(): " + mPackages);
+            }
+        }
+
+
+        @Override
+        public boolean filterApp(AppEntry info) {
+            return mPackages != null && mPackages.contains(info.info.packageName);
+        }
+    };
+
+    private final AppEntryListManager.Callback mCallback = new AppEntryListManager.Callback() {
+        @Override
+        public void onAppEntryListChanged(List<AppEntry> entries) {
+            mEntries = entries;
+            refreshUi();
+        }
+    };
+
+    @VisibleForTesting
+    AppEntryListManager mAppEntryListManager;
+    private List<AppEntry> mEntries;
+
+    public DirectoryAccessPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAppEntryListManager = new AppEntryListManager(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mAppEntryListManager.init(/* extraInfoBridge= */ null,
+                () -> FILTER_APP_HAS_DIRECTORY_ACCESS, mCallback);
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mAppEntryListManager.start();
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mAppEntryListManager.stop();
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mAppEntryListManager.destroy();
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        if (mEntries == null) {
+            // Still loading.
+            return;
+        }
+        preference.removeAll();
+        for (AppEntry entry : mEntries) {
+            Preference appPreference = new Preference(getContext());
+            String key = entry.info.packageName + "|" + entry.info.uid;
+            appPreference.setKey(key);
+            appPreference.setTitle(entry.label);
+            appPreference.setIcon(entry.icon);
+            appPreference.setOnPreferenceClickListener(clickedPref -> {
+                getFragmentController().launchFragment(
+                        DirectoryAccessDetailsFragment.getInstance(entry.info.packageName));
+                return true;
+            });
+            preference.addPreference(appPreference);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/ModifySystemSettingsFragment.java b/src/com/android/car/settings/applications/specialaccess/ModifySystemSettingsFragment.java
new file mode 100644
index 0000000..bd3955d
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/ModifySystemSettingsFragment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+
+/**
+ * Displays apps which have requested to modify system settings and their current allowed status.
+ */
+public class ModifySystemSettingsFragment extends AppOpsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.modify_system_settings_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        lookupAppOpsPreferenceController().init(AppOpsManager.OP_WRITE_SETTINGS,
+                Manifest.permission.WRITE_SETTINGS,
+                AppOpsManager.MODE_ERRORED);
+    }
+
+    @Override
+    protected AppOpsPreferenceController lookupAppOpsPreferenceController() {
+        return use(AppOpsPreferenceController.class, R.string.pk_modify_system_settings);
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/NotificationAccessFragment.java b/src/com/android/car/settings/applications/specialaccess/NotificationAccessFragment.java
new file mode 100644
index 0000000..8583b3d
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/NotificationAccessFragment.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Displays controls for managing notification listener permissions. */
+public class NotificationAccessFragment extends SettingsFragment {
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.notification_access_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/NotificationAccessPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/NotificationAccessPreferenceController.java
new file mode 100644
index 0000000..0f30329
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/NotificationAccessPreferenceController.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.Manifest;
+import android.app.NotificationManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.AsyncTask;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.util.IconDrawableFactory;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.ServiceListing;
+
+import java.util.List;
+
+/**
+ * Displays a list of notification listener services and provides toggles to allow the user to
+ * grant/revoke permission for listening to notifications. Before changing the value of a
+ * permission, the user is shown a confirmation dialog with information about the risks and
+ * potential effects.
+ */
+public class NotificationAccessPreferenceController extends PreferenceController<PreferenceGroup> {
+
+    private static final Logger LOG = new Logger(NotificationAccessPreferenceController.class);
+
+    @VisibleForTesting
+    static final String GRANT_CONFIRM_DIALOG_TAG =
+            "com.android.car.settings.applications.specialaccess.GrantNotificationAccessDialog";
+    @VisibleForTesting
+    static final String REVOKE_CONFIRM_DIALOG_TAG =
+            "com.android.car.settings.applications.specialaccess.RevokeNotificationAccessDialog";
+    private static final String KEY_SERVICE = "service";
+
+    private final NotificationManager mNm;
+    private final ServiceListing mServiceListing;
+    private final IconDrawableFactory mIconDrawableFactory;
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    private final ServiceListing.Callback mCallback = this::onServicesReloaded;
+
+    private final ConfirmationDialogFragment.ConfirmListener mGrantConfirmListener = arguments -> {
+        ComponentName service = arguments.getParcelable(KEY_SERVICE);
+        grantNotificationAccess(service);
+    };
+    private final ConfirmationDialogFragment.ConfirmListener mRevokeConfirmListener =
+            arguments -> {
+                ComponentName service = arguments.getParcelable(KEY_SERVICE);
+                revokeNotificationAccess(service);
+            };
+
+    public NotificationAccessPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mNm = context.getSystemService(NotificationManager.class);
+        mServiceListing = new ServiceListing.Builder(context)
+                .setPermission(Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE)
+                .setIntentAction(NotificationListenerService.SERVICE_INTERFACE)
+                .setSetting(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS)
+                .setTag(NotificationAccessPreferenceController.class.getSimpleName())
+                .setNoun("notification listener") // For logging.
+                .build();
+        mIconDrawableFactory = IconDrawableFactory.newInstance(context);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        ConfirmationDialogFragment grantConfirmDialogFragment =
+                (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
+                        GRANT_CONFIRM_DIALOG_TAG);
+        ConfirmationDialogFragment.resetListeners(grantConfirmDialogFragment,
+                mGrantConfirmListener, /* rejectListener= */ null);
+
+        ConfirmationDialogFragment revokeConfirmDialogFragment =
+                (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
+                        REVOKE_CONFIRM_DIALOG_TAG);
+        ConfirmationDialogFragment.resetListeners(revokeConfirmDialogFragment,
+                mRevokeConfirmListener, /* rejectListener= */ null);
+
+        mServiceListing.addCallback(mCallback);
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mServiceListing.reload();
+        mServiceListing.setListening(true);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mServiceListing.setListening(false);
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mServiceListing.removeCallback(mCallback);
+    }
+
+    private void onServicesReloaded(List<ServiceInfo> services) {
+        PackageManager packageManager = getContext().getPackageManager();
+        services.sort(new PackageItemInfo.DisplayNameComparator(packageManager));
+        getPreference().removeAll();
+        for (ServiceInfo service : services) {
+            ComponentName cn = new ComponentName(service.packageName, service.name);
+            CharSequence title = null;
+            try {
+                title = packageManager.getApplicationInfoAsUser(service.packageName, /* flags= */ 0,
+                        mCarUserManagerHelper.getCurrentProcessUserId()).loadLabel(packageManager);
+            } catch (PackageManager.NameNotFoundException e) {
+                LOG.e("can't find package name", e);
+            }
+            String summary = service.loadLabel(packageManager).toString();
+            SwitchPreference pref = new SwitchPreference(getContext());
+            pref.setPersistent(false);
+            pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo,
+                    UserHandle.getUserId(service.applicationInfo.uid)));
+            if (title != null && !title.equals(summary)) {
+                pref.setTitle(title);
+                pref.setSummary(summary);
+            } else {
+                pref.setTitle(summary);
+            }
+            pref.setKey(cn.flattenToString());
+            pref.setChecked(isAccessGranted(cn));
+            pref.setOnPreferenceChangeListener((preference, newValue) -> {
+                boolean enable = (boolean) newValue;
+                return promptUserToConfirmChange(cn, summary, enable);
+            });
+            getPreference().addPreference(pref);
+        }
+    }
+
+    private boolean isAccessGranted(ComponentName service) {
+        return mNm.isNotificationListenerAccessGranted(service);
+    }
+
+    private void grantNotificationAccess(ComponentName service) {
+        mNm.setNotificationListenerAccessGranted(service, /* granted= */ true);
+    }
+
+    private void revokeNotificationAccess(ComponentName service) {
+        mNm.setNotificationListenerAccessGranted(service, /* granted= */ false);
+        AsyncTask.execute(() -> {
+            if (!mNm.isNotificationPolicyAccessGrantedForPackage(service.getPackageName())) {
+                mNm.removeAutomaticZenRules(service.getPackageName());
+            }
+        });
+    }
+
+    private boolean promptUserToConfirmChange(ComponentName service, String label,
+            boolean grantAccess) {
+        if (isAccessGranted(service) == grantAccess) {
+            return true;
+        }
+        ConfirmationDialogFragment.Builder dialogFragment =
+                grantAccess ? createConfirmGrantDialogFragment(label)
+                        : createConfirmRevokeDialogFragment(label);
+        dialogFragment.addArgumentParcelable(KEY_SERVICE, service);
+        getFragmentController().showDialog(dialogFragment.build(),
+                grantAccess ? GRANT_CONFIRM_DIALOG_TAG : REVOKE_CONFIRM_DIALOG_TAG);
+        return false;
+    }
+
+    private ConfirmationDialogFragment.Builder createConfirmGrantDialogFragment(String label) {
+        String title = getContext().getResources().getString(
+                R.string.notification_listener_security_warning_title, label);
+        String summary = getContext().getResources().getString(
+                R.string.notification_listener_security_warning_summary, label);
+        return new ConfirmationDialogFragment.Builder(getContext())
+                .setTitle(title)
+                .setMessage(summary)
+                .setPositiveButton(R.string.allow, mGrantConfirmListener)
+                .setNegativeButton(R.string.deny, /* rejectionListener= */ null);
+    }
+
+    private ConfirmationDialogFragment.Builder createConfirmRevokeDialogFragment(String label) {
+        String summary = getContext().getResources().getString(
+                R.string.notification_listener_revoke_warning_summary, label);
+        return new ConfirmationDialogFragment.Builder(getContext())
+                .setMessage(summary)
+                .setPositiveButton(R.string.notification_listener_revoke_warning_confirm,
+                        mRevokeConfirmListener)
+                .setNegativeButton(R.string.notification_listener_revoke_warning_cancel,
+                        /* rejectionListener= */ null);
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessEntryPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessEntryPreferenceController.java
new file mode 100644
index 0000000..3270b0e
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessEntryPreferenceController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller for the entry point to premium SMS access settings. It reads an overlayable config
+ * value to determine if premium SMS is supported and returns an appropriate availability status.
+ */
+public class PremiumSmsAccessEntryPreferenceController extends PreferenceController<Preference> {
+
+    public PremiumSmsAccessEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return getContext().getResources().getBoolean(R.bool.config_show_premium_sms) ? AVAILABLE
+                : UNSUPPORTED_ON_DEVICE;
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessFragment.java b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessFragment.java
new file mode 100644
index 0000000..d485581
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Displays controls for managing premium SMS access. */
+public class PremiumSmsAccessFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.premium_sms_access_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceController.java
new file mode 100644
index 0000000..1673dcb
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceController.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import java.util.List;
+
+/**
+ * Displays the list of apps which have a known premium SMS access state. When a user selects an
+ * app, they are shown a dialog which allows them to configure the state to one of:
+ *
+ * <ul>
+ * <li>Ask - the user will be prompted before app sends premium SMS.
+ * <li>Never allow - app can never send premium SMS.
+ * <li>Always allow - app can automatically send premium SMS.
+ * </ul>
+ */
+public class PremiumSmsAccessPreferenceController extends PreferenceController<PreferenceGroup> {
+
+    private static final Logger LOG = new Logger(PremiumSmsAccessPreferenceController.class);
+
+    private static final AppFilter FILTER_SMS_STATE_KNOWN = new AppFilter() {
+        @Override
+        public void init() {
+            // No op.
+        }
+
+        @Override
+        public boolean filterApp(AppEntry info) {
+            return info.extraInfo != null
+                    && (Integer) info.extraInfo != SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN;
+        }
+    };
+
+    private final ISms mSmsManager;
+
+    private final Preference.OnPreferenceChangeListener mOnPreferenceChangeListener =
+            new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newValue) {
+                    PremiumSmsPreference appPreference = (PremiumSmsPreference) preference;
+                    AppEntry entry = appPreference.mEntry;
+                    int smsState = Integer.parseInt((String) newValue);
+                    if (smsState != (Integer) entry.extraInfo) {
+                        try {
+                            mSmsManager.setPremiumSmsPermission(entry.info.packageName, smsState);
+                        } catch (RemoteException e) {
+                            LOG.w("Unable to set premium sms permission for "
+                                    + entry.info.packageName + " " + entry.info.uid, e);
+                            return false;
+                        }
+                        // Update the extra info of this entry so that it reflects the new state.
+                        mAppEntryListManager.forceUpdate(entry);
+                        return true;
+                    }
+                    return false;
+                }
+            };
+
+    private final AppEntryListManager.Callback mCallback = new AppEntryListManager.Callback() {
+        @Override
+        public void onAppEntryListChanged(List<AppEntry> entries) {
+            mEntries = entries;
+            refreshUi();
+        }
+    };
+
+    @VisibleForTesting
+    AppEntryListManager mAppEntryListManager;
+    private List<AppEntry> mEntries;
+
+    public PremiumSmsAccessPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mSmsManager = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+        mAppEntryListManager = new AppEntryListManager(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mAppEntryListManager.init(new AppStatePremiumSmsBridge(mSmsManager),
+                () -> FILTER_SMS_STATE_KNOWN, mCallback);
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mAppEntryListManager.start();
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mAppEntryListManager.stop();
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mAppEntryListManager.destroy();
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        if (mEntries == null) {
+            // Still loading.
+            return;
+        }
+        preference.removeAll();
+        for (AppEntry entry : mEntries) {
+            Preference appPreference = new PremiumSmsPreference(getContext(), entry);
+            appPreference.setOnPreferenceChangeListener(mOnPreferenceChangeListener);
+            preference.addPreference(appPreference);
+        }
+    }
+
+    private static class PremiumSmsPreference extends ListPreference {
+
+        private final AppEntry mEntry;
+
+        PremiumSmsPreference(Context context, AppEntry entry) {
+            super(context);
+            String key = entry.info.packageName + "|" + entry.info.uid;
+            setKey(key);
+            setTitle(entry.label);
+            setIcon(entry.icon);
+            setPersistent(false);
+            setEntries(R.array.premium_sms_access_values);
+            setEntryValues(new CharSequence[]{
+                    String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER),
+                    String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW),
+                    String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW)
+            });
+            setValue(String.valueOf(entry.extraInfo));
+            setSummary("%s");
+            mEntry = entry;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/SpecialAccessSettingsFragment.java b/src/com/android/car/settings/applications/specialaccess/SpecialAccessSettingsFragment.java
new file mode 100644
index 0000000..3c89a71
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/SpecialAccessSettingsFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Top-level page which lists the types of special access the user can grant to applications.
+ * Selecting an option will open a detailed page for granting access to individual apps.
+ */
+public class SpecialAccessSettingsFragment extends SettingsFragment {
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.special_access_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/UsageAccessFragment.java b/src/com/android/car/settings/applications/specialaccess/UsageAccessFragment.java
new file mode 100644
index 0000000..c44e870
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/UsageAccessFragment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+
+/**
+ * Displays apps which have requested to access usage data and their current allowed status.
+ */
+public class UsageAccessFragment extends AppOpsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.usage_access_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        lookupAppOpsPreferenceController().init(AppOpsManager.OP_GET_USAGE_STATS,
+                Manifest.permission.PACKAGE_USAGE_STATS,
+                AppOpsManager.MODE_IGNORED);
+    }
+
+    @Override
+    protected AppOpsPreferenceController lookupAppOpsPreferenceController() {
+        return use(AppOpsPreferenceController.class, R.string.pk_usage_access);
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/WifiControlFragment.java b/src/com/android/car/settings/applications/specialaccess/WifiControlFragment.java
new file mode 100644
index 0000000..eff706c
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/WifiControlFragment.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+
+/**
+ * Displays apps which have requested to control Wi-Fi settings and their current allowed status.
+ */
+public class WifiControlFragment extends AppOpsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_control_fragment;
+    }
+
+    @Override
+    protected AppOpsPreferenceController lookupAppOpsPreferenceController() {
+        return use(WifiControlPreferenceController.class, R.string.pk_wifi_control);
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/WifiControlPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/WifiControlPreferenceController.java
new file mode 100644
index 0000000..8780f1f
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/WifiControlPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.applications.specialaccess.AppStateAppOpsBridge.PermissionState;
+import com.android.car.settings.common.FragmentController;
+import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
+
+/**
+ * Manages the list of apps requesting to control Wi-Fi settings. Apps that also request {@link
+ * Manifest.permission#NETWORK_SETTINGS} are excluded from the list as this permission overrules
+ * {@link Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+public class WifiControlPreferenceController extends AppOpsPreferenceController {
+
+    private static final AppFilter FILTER_CHANGE_WIFI_STATE = new AppFilter() {
+        @Override
+        public void init() {
+            // No op.
+        }
+
+        @Override
+        public boolean filterApp(ApplicationsState.AppEntry info) {
+            return !ArrayUtils.contains(
+                    ((PermissionState) info.extraInfo).getRequestedPermissions(),
+                    Manifest.permission.NETWORK_SETTINGS);
+        }
+    };
+
+    public WifiControlPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        init(AppOpsManager.OP_CHANGE_WIFI_STATE, Manifest.permission.CHANGE_WIFI_STATE,
+                AppOpsManager.MODE_IGNORED);
+    }
+
+    @Override
+    protected AppFilter getAppFilter() {
+        AppFilter filter = super.getAppFilter();
+        return new CompoundFilter(filter, FILTER_CHANGE_WIFI_STATE);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothAddressPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothAddressPreferenceController.java
new file mode 100644
index 0000000..e5e599b
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothAddressPreferenceController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Displays the Bluetooth MAC address of the vehicle.
+ */
+public class BluetoothAddressPreferenceController extends
+        BluetoothPreferenceController<Preference> {
+
+    public BluetoothAddressPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        String address = BluetoothAdapter.getDefaultAdapter().getAddress();
+        String formattedAddress = getContext().getString(R.string.bluetooth_vehicle_mac_address,
+                address);
+        preference.setTitle(formattedAddress);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothBondedDevicesPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothBondedDevicesPreferenceController.java
new file mode 100644
index 0000000..8402974
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothBondedDevicesPreferenceController.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+/**
+ * Displays a list of bonded (paired) Bluetooth devices. Clicking on a device will attempt a
+ * connection with that device. If a device is already connected, a click will prompt the user to
+ * disconnect. Devices are shown with an addition affordance which launches the device details page.
+ */
+public class BluetoothBondedDevicesPreferenceController extends
+        BluetoothDevicesGroupPreferenceController {
+
+    public BluetoothBondedDevicesPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected BluetoothDeviceFilter.Filter getDeviceFilter() {
+        return BluetoothDeviceFilter.BONDED_DEVICE_FILTER;
+    }
+
+    @Override
+    protected BluetoothDevicePreference createDevicePreference(CachedBluetoothDevice cachedDevice) {
+        BluetoothDevicePreference pref = super.createDevicePreference(cachedDevice);
+        if (!getCarUserManagerHelper().isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)) {
+            pref.setWidgetLayoutResource(R.layout.details_preference_widget);
+            pref.setOnButtonClickListener(preference -> getFragmentController().launchFragment(
+                    BluetoothDeviceDetailsFragment.newInstance(cachedDevice)));
+            pref.showAction(true);
+        }
+        return pref;
+    }
+
+    @Override
+    protected void onDeviceClicked(CachedBluetoothDevice cachedDevice) {
+        if (cachedDevice.isConnected()) {
+            getFragmentController().showDialog(
+                    BluetoothDisconnectConfirmDialogFragment.newInstance(cachedDevice),
+                    /* tag= */ null);
+        } else {
+            cachedDevice.connect(/* connectAllProfiles= */ true);
+        }
+    }
+
+    @Override
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        refreshUi();
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDetailFragment.java b/src/com/android/car/settings/bluetooth/BluetoothDetailFragment.java
deleted file mode 100644
index 2a27f58..0000000
--- a/src/com/android/car/settings/bluetooth/BluetoothDetailFragment.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.settings.bluetooth;
-
-import android.bluetooth.BluetoothDevice;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.Button;
-
-import com.android.car.list.EditTextLineItem;
-import com.android.car.list.SingleTextLineItem;
-import com.android.car.list.TypedPagedListAdapter;
-import com.android.car.settings.R;
-import com.android.car.settings.common.ListSettingsFragment;
-import com.android.car.settings.common.Logger;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
-
-import java.util.ArrayList;
-
-/**
- * Shows details about a bluetooth device, including actions related to the device,
- * e.g. forget etc. The intent should include information about the device, use that to
- * render UI, e.g. show name etc.
- */
-public class BluetoothDetailFragment extends ListSettingsFragment implements
-        BluetoothProfileLineItem.DataChangedListener {
-    private static final Logger LOG = new Logger(BluetoothDetailFragment.class);
-
-    public static final String EXTRA_BT_DEVICE = "extra_bt_device";
-
-    private BluetoothDevice mDevice;
-    private CachedBluetoothDevice mCachedDevice;
-
-    private CachedBluetoothDeviceManager mDeviceManager;
-    private LocalBluetoothManager mLocalManager;
-    private EditTextLineItem mInputLineItem;
-    private Button mOkButton;
-
-    public static BluetoothDetailFragment getInstance(BluetoothDevice btDevice) {
-        BluetoothDetailFragment bluetoothDetailFragment = new BluetoothDetailFragment();
-        Bundle bundle = ListSettingsFragment.getBundle();
-        bundle.putParcelable(EXTRA_BT_DEVICE, btDevice);
-        bundle.putInt(EXTRA_TITLE_ID, R.string.bluetooth_settings);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        bluetoothDetailFragment.setArguments(bundle);
-        return bluetoothDetailFragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mDevice = getArguments().getParcelable(EXTRA_BT_DEVICE);
-        mLocalManager =
-                LocalBluetoothManager.getInstance(getContext(), /* onInitCallback= */ null);
-        if (mLocalManager == null) {
-            LOG.e("Bluetooth is not supported on this device");
-            return;
-        }
-        mDeviceManager = mLocalManager.getCachedDeviceManager();
-        mCachedDevice = mDeviceManager.findDevice(mDevice);
-        if (mCachedDevice == null) {
-            mCachedDevice = mDeviceManager.addDevice(
-                    mLocalManager.getBluetoothAdapter(),
-                    mLocalManager.getProfileManager(),
-                    mDevice);
-        }
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        if (mDevice == null) {
-            LOG.w("No bluetooth device set.");
-            return;
-        }
-        super.onActivityCreated(savedInstanceState);
-
-        setupForgetButton();
-        setupOkButton();
-    }
-
-    @Override
-    public void onDataChanged() {
-        mPagedListAdapter.notifyDataSetChanged();
-    }
-
-    @Override
-    public ArrayList<TypedPagedListAdapter.LineItem> getLineItems() {
-        ArrayList<TypedPagedListAdapter.LineItem> lineItems = new ArrayList<>();
-        mInputLineItem = new EditTextLineItem(
-                getContext().getText(R.string.bluetooth_preference_paired_dialog_name_label),
-                mCachedDevice.getName());
-        mInputLineItem.setTextType(EditTextLineItem.TextType.TEXT);
-        lineItems.add(mInputLineItem);
-        lineItems.add(new SingleTextLineItem(getContext().getText(
-                R.string.bluetooth_device_advanced_profile_header_title)));
-        addProfileLineItems(lineItems);
-        return lineItems;
-    }
-
-    private void addProfileLineItems(ArrayList<TypedPagedListAdapter.LineItem> lineItems) {
-        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
-            lineItems.add(new BluetoothProfileLineItem(
-                    getContext(), profile, mCachedDevice, this));
-        }
-    }
-
-    private void setupForgetButton() {
-        Button fortgetButton = getActivity().findViewById(R.id.action_button2);
-        fortgetButton.setVisibility(View.VISIBLE);
-        fortgetButton.setText(R.string.forget);
-        fortgetButton.setOnClickListener(v -> {
-            mCachedDevice.unpair();
-            getFragmentController().goBack();
-        });
-    }
-
-    private void setupOkButton() {
-        mOkButton = getActivity().findViewById(R.id.action_button1);
-        mOkButton.setText(android.R.string.ok);
-        mOkButton.setOnClickListener(v -> {
-            if (!mInputLineItem.getInput().equals(mCachedDevice.getName())) {
-                mCachedDevice.setName(mInputLineItem.getInput());
-            }
-            getFragmentController().goBack();
-        });
-    }
-}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDeviceAddressPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothDeviceAddressPreferenceController.java
new file mode 100644
index 0000000..e2e74bf
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDeviceAddressPreferenceController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Displays the Bluetooth MAC address of a remote device.
+ */
+public class BluetoothDeviceAddressPreferenceController extends
+        BluetoothDevicePreferenceController<Preference> {
+
+    public BluetoothDeviceAddressPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        String address = getContext().getString(R.string.bluetooth_device_mac_address,
+                getCachedDevice().getAddress());
+        preference.setTitle(address);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/car/settings/bluetooth/BluetoothDeviceDetailsFragment.java
new file mode 100644
index 0000000..0c15570
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/**
+ * Page which displays information for a remote Bluetooth device including the name, icon,
+ * connection status, supported profiles, and MAC. Buttons are shown to enable
+ * connecting/disconnecting and forgetting the device.
+ */
+public class BluetoothDeviceDetailsFragment extends SettingsFragment {
+
+    private static final String KEY_DEVICE_ADDRESS = "device_address";
+
+    private final CachedBluetoothDevice.Callback mDeviceCallback =
+            this::updateConnectionButtonState;
+
+    private CachedBluetoothDevice mCachedDevice;
+    private Button mConnectionButton;
+
+    /**
+     * Returns a new {@link BluetoothDeviceDetailsFragment} for the given {@code device}.
+     */
+    public static BluetoothDeviceDetailsFragment newInstance(CachedBluetoothDevice device) {
+        Bundle args = new Bundle();
+        args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, device.getAddress());
+        BluetoothDeviceDetailsFragment fragment = new BluetoothDeviceDetailsFragment();
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.bluetooth_device_details_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        LocalBluetoothManager manager = BluetoothUtils.getLocalBtManager(context);
+        if (manager == null) {
+            goBack();
+            return;
+        }
+        String deviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
+        BluetoothDevice remoteDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+                deviceAddress);
+        mCachedDevice = manager.getCachedDeviceManager().findDevice(remoteDevice);
+        if (mCachedDevice == null) {
+            goBack();
+            return;
+        }
+
+        use(BluetoothDeviceNamePreferenceController.class,
+                R.string.pk_bluetooth_device_name).setCachedDevice(mCachedDevice);
+        use(BluetoothDeviceProfilesPreferenceController.class,
+                R.string.pk_bluetooth_device_profiles).setCachedDevice(mCachedDevice);
+        use(BluetoothDeviceAddressPreferenceController.class,
+                R.string.pk_bluetooth_device_address).setCachedDevice(mCachedDevice);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setupForgetButton();
+        setupConnectionButton();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mCachedDevice.registerCallback(mDeviceCallback);
+        updateConnectionButtonState();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mCachedDevice.unregisterCallback(mDeviceCallback);
+    }
+
+    private void setupForgetButton() {
+        Button forgetButton = requireActivity().findViewById(R.id.action_button2);
+        forgetButton.setVisibility(View.VISIBLE);
+        forgetButton.setText(R.string.forget);
+        forgetButton.setOnClickListener(v -> {
+            mCachedDevice.unpair();
+            goBack();
+        });
+    }
+
+    private void setupConnectionButton() {
+        mConnectionButton = requireActivity().findViewById(R.id.action_button1);
+        updateConnectionButtonState();
+    }
+
+    private void updateConnectionButtonState() {
+        mConnectionButton.setEnabled(!mCachedDevice.isBusy());
+        if (mCachedDevice.isConnected()) {
+            mConnectionButton.setText(R.string.disconnect);
+            mConnectionButton.setOnClickListener(view -> mCachedDevice.disconnect());
+        } else {
+            mConnectionButton.setText(R.string.connect);
+            mConnectionButton.setOnClickListener(
+                    view -> mCachedDevice.connect(/* connectAllProfiles= */ true));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDeviceListAdapter.java b/src/com/android/car/settings/bluetooth/BluetoothDeviceListAdapter.java
deleted file mode 100644
index 74f6bd0..0000000
--- a/src/com/android/car/settings/bluetooth/BluetoothDeviceListAdapter.java
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.settings.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemProperties;
-import android.support.v7.widget.RecyclerView;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.car.widget.PagedListView;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.Logger;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.HidProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Renders {@link android.bluetooth.BluetoothDevice} to a view to be displayed as a row in a list.
- */
-public class BluetoothDeviceListAdapter
-        extends RecyclerView.Adapter<BluetoothDeviceListAdapter.ViewHolder>
-        implements PagedListView.ItemCap, BluetoothCallback {
-    private static final Logger LOG = new Logger(BluetoothDeviceListAdapter.class);
-    // Copied from BluetoothDeviceNoNamePreferenceController.java
-    private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
-            "persist.bluetooth.showdeviceswithoutnames";
-    private static final int DEVICE_ROW_TYPE = 1;
-    private static final int BONDED_DEVICE_HEADER_TYPE = 2;
-    private static final int AVAILABLE_DEVICE_HEADER_TYPE = 3;
-    private static final int NUM_OF_HEADERS = 2;
-    public static final int DELAY_MILLIS = 1000;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private final HashSet<CachedBluetoothDevice> mBondedDevices = new HashSet<>();
-    private final HashSet<CachedBluetoothDevice> mAvailableDevices = new HashSet<>();
-    private final LocalBluetoothAdapter mLocalAdapter;
-    private final LocalBluetoothManager mLocalManager;
-    private final CachedBluetoothDeviceManager mDeviceManager;
-    private final Context mContext;
-    private final BaseFragment.FragmentController mFragmentController;
-    private final boolean mShowDevicesWithoutNames;
-
-    /* Talk-back descriptions for various BT icons */
-    public final String mComputerDescription;
-    public final String mInputPeripheralDescription;
-    public final String mHeadsetDescription;
-    public final String mPhoneDescription;
-    public final String mImagingDescription;
-    public final String mHeadphoneDescription;
-    public final String mBluetoothDescription;
-
-    private SortTask mSortTask;
-
-    private ArrayList<CachedBluetoothDevice> mBondedDevicesSorted = new ArrayList<>();
-    private ArrayList<CachedBluetoothDevice> mAvailableDevicesSorted = new ArrayList<>();
-
-    class ViewHolder extends RecyclerView.ViewHolder {
-        private final ImageView mIcon;
-        private final TextView mTitle;
-        private final TextView mDesc;
-        private final ImageButton mActionButton;
-        private final DeviceAttributeChangeCallback mCallback =
-                new DeviceAttributeChangeCallback(this);
-
-        public ViewHolder(View view) {
-            super(view);
-            mTitle = (TextView) view.findViewById(R.id.title);
-            mDesc = (TextView) view.findViewById(R.id.desc);
-            mIcon = (ImageView) view.findViewById(R.id.icon);
-            mActionButton = (ImageButton) view.findViewById(R.id.action);
-            view.setOnClickListener(new BluetoothClickListener(this));
-        }
-    }
-
-    public BluetoothDeviceListAdapter(
-            Context context,
-            LocalBluetoothManager localBluetoothManager,
-            BaseFragment.FragmentController fragmentController) {
-        mContext = context;
-        mLocalManager = localBluetoothManager;
-        mFragmentController = fragmentController;
-        mLocalAdapter = mLocalManager.getBluetoothAdapter();
-        mDeviceManager = mLocalManager.getCachedDeviceManager();
-
-        Resources r = context.getResources();
-        mComputerDescription = r.getString(R.string.bluetooth_talkback_computer);
-        mInputPeripheralDescription = r.getString(
-                R.string.bluetooth_talkback_input_peripheral);
-        mHeadsetDescription = r.getString(R.string.bluetooth_talkback_headset);
-        mPhoneDescription = r.getString(R.string.bluetooth_talkback_phone);
-        mImagingDescription = r.getString(R.string.bluetooth_talkback_imaging);
-        mHeadphoneDescription = r.getString(R.string.bluetooth_talkback_headphone);
-        mBluetoothDescription = r.getString(R.string.bluetooth_talkback_bluetooth);
-        mShowDevicesWithoutNames =
-                SystemProperties.getBoolean(BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
-    }
-
-    public void start() {
-        mLocalManager.getEventManager().registerCallback(this);
-        if (mLocalAdapter.isEnabled()) {
-            mLocalAdapter.startScanning(true);
-            addBondDevices();
-            addCachedDevices();
-        }
-        // create task here to avoid re-executing existing tasks.
-        mSortTask = new SortTask();
-        mSortTask.execute();
-    }
-
-    public void stop() {
-        mLocalAdapter.stopScanning();
-        mDeviceManager.clearNonBondedDevices();
-        mLocalManager.getEventManager().unregisterCallback(this);
-        mBondedDevices.clear();
-        mBondedDevicesSorted.clear();
-        mAvailableDevices.clear();
-        mAvailableDevicesSorted.clear();
-        mSortTask.cancel(true);
-    }
-
-    @Override
-    public BluetoothDeviceListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
-            int viewType) {
-        View v;
-        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
-        switch (viewType) {
-            case BONDED_DEVICE_HEADER_TYPE:
-                v = layoutInflater.inflate(R.layout.single_text_line_item, parent, false);
-                v.setEnabled(false);
-                ((TextView) v.findViewById(R.id.title)).setText(
-                        R.string.bluetooth_preference_paired_devices);
-                break;
-            case AVAILABLE_DEVICE_HEADER_TYPE:
-                v = layoutInflater.inflate(R.layout.single_text_line_item, parent, false);
-                v.setEnabled(false);
-                ((TextView) v.findViewById(R.id.title)).setText(
-                        R.string.bluetooth_preference_found_devices);
-                break;
-            default:
-                v = layoutInflater.inflate(R.layout.icon_widget_line_item, parent, false);
-        }
-        return new ViewHolder(v);
-    }
-
-    @Override
-    public int getItemCount() {
-        return mAvailableDevicesSorted.size() + NUM_OF_HEADERS + mBondedDevicesSorted.size();
-    }
-
-    @Override
-    public void setMaxItems(int maxItems) {
-        // no limit in this list.
-    }
-
-    @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        final CachedBluetoothDevice bluetoothDevice = getItem(position);
-        if (bluetoothDevice == null) {
-            // this row is for in-list headers
-            return;
-        }
-        if (holder.getOldPosition() != RecyclerView.NO_POSITION) {
-            getItem(holder.getOldPosition()).unregisterCallback(holder.mCallback);
-        }
-        bluetoothDevice.registerCallback(holder.mCallback);
-        holder.mTitle.setText(bluetoothDevice.getName());
-        Pair<Integer, String> pair = getBtClassDrawableWithDescription(bluetoothDevice);
-        holder.mIcon.setImageResource(pair.first);
-        String summaryText = bluetoothDevice.getCarConnectionSummary();
-        if (summaryText != null) {
-            holder.mDesc.setText(summaryText);
-            holder.mDesc.setVisibility(View.VISIBLE);
-        } else {
-            holder.mDesc.setVisibility(View.GONE);
-        }
-        if (BluetoothDeviceFilter.BONDED_DEVICE_FILTER.matches(bluetoothDevice.getDevice())) {
-            holder.mActionButton.setVisibility(View.VISIBLE);
-            holder.mActionButton.setOnClickListener(v -> {
-                mFragmentController.launchFragment(
-                        BluetoothDetailFragment.getInstance(bluetoothDevice.getDevice()));
-                });
-        } else {
-            holder.mActionButton.setVisibility(View.GONE);
-        }
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        // the first row is the header for the bonded device list;
-        if (position == 0) {
-            return BONDED_DEVICE_HEADER_TYPE;
-        }
-        // after the end of the bonded device list is the header of the available device list.
-        if (position == mBondedDevicesSorted.size() + 1) {
-            return AVAILABLE_DEVICE_HEADER_TYPE;
-        }
-        return DEVICE_ROW_TYPE;
-    }
-
-    private CachedBluetoothDevice getItem(int position) {
-        if (position > 0 && position <= mBondedDevicesSorted.size()) {
-            // off set the header row
-            return mBondedDevicesSorted.get(position - 1);
-        }
-        if (position > mBondedDevicesSorted.size() + 1
-                && position <= mBondedDevicesSorted.size() + 1 + mAvailableDevicesSorted.size()) {
-            // off set two header row and the size of bonded device list.
-            return mAvailableDevicesSorted.get(
-                    position - NUM_OF_HEADERS - mBondedDevicesSorted.size());
-        }
-        // otherwise it's a in list header
-        return null;
-    }
-
-    // callback functions
-    @Override
-    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-        if (addDevice(cachedDevice)) {
-            ArrayList<CachedBluetoothDevice> devices = new ArrayList<>(mBondedDevices);
-            Collections.sort(devices);
-            mBondedDevicesSorted = devices;
-            notifyDataSetChanged();
-        }
-    }
-
-    @Override
-    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        // the device might changed bonding state, so need to remove from both sets.
-        if (mBondedDevices.remove(cachedDevice)) {
-            mBondedDevicesSorted.remove(cachedDevice);
-        }
-        mAvailableDevices.remove(cachedDevice);
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public void onBluetoothStateChanged(int bluetoothState) {
-        switch (bluetoothState) {
-            case BluetoothAdapter.STATE_OFF:
-                mBondedDevices.clear();
-                mBondedDevicesSorted.clear();
-                mAvailableDevices.clear();
-                mAvailableDevicesSorted.clear();
-                notifyDataSetChanged();
-                break;
-            case BluetoothAdapter.STATE_ON:
-                mLocalAdapter.startScanning(true);
-                addBondDevices();
-                addCachedDevices();
-                break;
-            default:
-        }
-    }
-
-    public void reset() {
-        mBondedDevices.clear();
-        mBondedDevicesSorted.clear();
-        mAvailableDevices.clear();
-        mAvailableDevicesSorted.clear();
-        mLocalAdapter.startScanning(true);
-        addBondDevices();
-        addCachedDevices();
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public void onScanningStateChanged(boolean started) {
-        // don't care
-    }
-
-    @Override
-    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-        onDeviceDeleted(cachedDevice);
-        onDeviceAdded(cachedDevice);
-    }
-
-    /**
-     * Call back for the first connection or the last connection to ANY device/profile. Not
-     * suitable for monitor per device level connection.
-     */
-    @Override
-    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-        onDeviceDeleted(cachedDevice);
-        onDeviceAdded(cachedDevice);
-    }
-
-    @Override
-    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
-        // Not used (for now)
-    }
-
-    @Override
-    public void onAudioModeChanged() {
-        // Not used (for now)
-    }
-
-    private void addDevices(Collection<CachedBluetoothDevice> cachedDevices) {
-        boolean needSort = false;
-        for (CachedBluetoothDevice device : cachedDevices) {
-            if (addDevice(device)) {
-                needSort = true;
-            }
-        }
-        if (needSort) {
-            ArrayList<CachedBluetoothDevice> devices =
-                    new ArrayList<CachedBluetoothDevice>(mBondedDevices);
-            Collections.sort(devices);
-            mBondedDevicesSorted = devices;
-            notifyDataSetChanged();
-        }
-    }
-
-    /**
-     * @return {@code true} if list changed and needed sort again.
-     */
-    private boolean addDevice(CachedBluetoothDevice cachedDevice) {
-        boolean needSort = false;
-        if (BluetoothDeviceFilter.BONDED_DEVICE_FILTER.matches(cachedDevice.getDevice())) {
-            if (mBondedDevices.add(cachedDevice)) {
-                needSort = true;
-            }
-        }
-        if (BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER.matches(cachedDevice.getDevice())
-                && (mShowDevicesWithoutNames || cachedDevice.hasHumanReadableName())) {
-            // reset is done at SortTask.
-            mAvailableDevices.add(cachedDevice);
-        }
-        return needSort;
-    }
-
-    private void addBondDevices() {
-        Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
-        if (bondedDevices == null) {
-            return;
-        }
-        ArrayList<CachedBluetoothDevice> cachedBluetoothDevices = new ArrayList<>();
-        for (BluetoothDevice device : bondedDevices) {
-            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
-            if (cachedDevice == null) {
-                cachedDevice = mDeviceManager.addDevice(
-                        mLocalAdapter, mLocalManager.getProfileManager(), device);
-            }
-            cachedBluetoothDevices.add(cachedDevice);
-        }
-        addDevices(cachedBluetoothDevices);
-    }
-
-    private void addCachedDevices() {
-        addDevices(mDeviceManager.getCachedDevicesCopy());
-    }
-
-    private Pair<Integer, String> getBtClassDrawableWithDescription(
-            CachedBluetoothDevice bluetoothDevice) {
-        BluetoothClass btClass = bluetoothDevice.getBtClass();
-        if (btClass != null) {
-            switch (btClass.getMajorDeviceClass()) {
-                case BluetoothClass.Device.Major.COMPUTER:
-                    return new Pair<>(R.drawable.ic_bt_laptop, mComputerDescription);
-
-                case BluetoothClass.Device.Major.PHONE:
-                    return new Pair<>(R.drawable.ic_bt_cellphone, mPhoneDescription);
-
-                case BluetoothClass.Device.Major.PERIPHERAL:
-                    return new Pair<>(HidProfile.getHidClassDrawable(btClass),
-                            mInputPeripheralDescription);
-
-                case BluetoothClass.Device.Major.IMAGING:
-                    return new Pair<>(R.drawable.ic_bt_imaging, mImagingDescription);
-
-                default:
-                    // unrecognized device class; continue
-            }
-        } else {
-            LOG.w("btClass is null");
-        }
-
-        List<LocalBluetoothProfile> profiles = bluetoothDevice.getProfiles();
-        for (LocalBluetoothProfile profile : profiles) {
-            int resId = profile.getDrawableResource(btClass);
-            if (resId != 0) {
-                return new Pair<Integer, String>(resId, null);
-            }
-        }
-        if (btClass != null) {
-            if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
-                return new Pair<Integer, String>(R.drawable.ic_bt_headset_hfp, mHeadsetDescription);
-            }
-            if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
-                return new Pair<Integer, String>(R.drawable.ic_bt_headphones_a2dp,
-                        mHeadphoneDescription);
-            }
-        }
-        return new Pair<Integer, String>(R.drawable.ic_settings_bluetooth, mBluetoothDescription);
-    }
-
-    /**
-     * Updates device render upon device attribute change.
-     */
-    // TODO: This is a walk around for handling attribute callback. Since the callback doesn't
-    // contain the information about which device needs to be updated, we have to maintain a
-    // local reference to the device. Fix the code in CachedBluetoothDevice.Callback to return
-    // a reference of the device been updated.
-    private class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
-
-        private final ViewHolder mViewHolder;
-
-        DeviceAttributeChangeCallback(ViewHolder viewHolder) {
-            mViewHolder = viewHolder;
-        }
-
-        @Override
-        public void onDeviceAttributesChanged() {
-            notifyItemChanged(mViewHolder.getAdapterPosition());
-        }
-    }
-
-    private class BluetoothClickListener implements OnClickListener {
-        private final ViewHolder mViewHolder;
-
-        BluetoothClickListener(ViewHolder viewHolder) {
-            mViewHolder = viewHolder;
-        }
-
-        @Override
-        public void onClick(View v) {
-            CachedBluetoothDevice device = getItem(mViewHolder.getAdapterPosition());
-            int bondState = device.getBondState();
-
-            if (device.isConnected()) {
-                // TODO: ask user for confirmation
-                device.disconnect();
-            } else if (bondState == BluetoothDevice.BOND_BONDED) {
-                device.connect(true);
-            } else if (bondState == BluetoothDevice.BOND_NONE) {
-                if (!device.startPairing()) {
-                    showError(device.getName(),
-                            R.string.bluetooth_pairing_error_message);
-                    return;
-                }
-                // allow MAP and PBAP since this is client side, permission should be handled on
-                // server side. i.e. the phone side.
-                device.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
-                device.setMessagePermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
-            }
-        }
-    }
-
-    private void showError(String name, int messageResId) {
-        String message = mContext.getString(messageResId, name);
-        Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
-    }
-
-    /**
-     * Provides an ordered bt device list periodically.
-     */
-    // TODO: improve the way we sort BT devices. Ideally we should keep all devices in a TreeSet
-    // and as devices are added the correct order is maintained, that requires a consistent
-    // logic between equals and compareTo function, unfortunately it's not the case in
-    // CachedBluetoothDevice class. Fix that and improve the way we order devices.
-    private class SortTask extends AsyncTask<Void, Void, ArrayList<CachedBluetoothDevice>> {
-
-        /**
-         * Returns {code null} if no changed are made.
-         */
-        @Override
-        protected ArrayList<CachedBluetoothDevice> doInBackground(Void... v) {
-            if (mAvailableDevicesSorted != null
-                    && mAvailableDevicesSorted.size() == mAvailableDevices.size()) {
-                return null;
-            }
-            ArrayList<CachedBluetoothDevice> devices =
-                    new ArrayList<CachedBluetoothDevice>(mAvailableDevices);
-            Collections.sort(devices);
-            return devices;
-        }
-
-        @Override
-        protected void onPostExecute(ArrayList<CachedBluetoothDevice> devices) {
-            // skip if no changes are made.
-            if (devices != null) {
-                mAvailableDevicesSorted = devices;
-                notifyDataSetChanged();
-            }
-            mHandler.postDelayed(new Runnable() {
-                public void run() {
-                    mSortTask = new SortTask();
-                    mSortTask.execute();
-                }
-            }, DELAY_MILLIS);
-        }
-    }
-}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDeviceNamePreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothDeviceNamePreferenceController.java
new file mode 100644
index 0000000..9ca375a
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDeviceNamePreferenceController.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import androidx.preference.Preference;
+
+import com.android.car.apps.common.util.Themes;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import java.util.StringJoiner;
+
+/**
+ * Displays the name, icon, and status (connected/disconnected, etc.) of a remote Bluetooth device.
+ * When the associated preference is clicked, a dialog is shown to allow the user to update the
+ * display name of the remote device.
+ */
+public class BluetoothDeviceNamePreferenceController extends
+        BluetoothDevicePreferenceController<Preference> {
+
+    public BluetoothDeviceNamePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        CachedBluetoothDevice cachedDevice = getCachedDevice();
+        Pair<Drawable, String> pair =
+                com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription(
+                        getContext(),
+                        cachedDevice,
+                        getContext().getResources().getFraction(
+                                R.fraction.bt_battery_scale_fraction, /* base= */1, /* pbase= */
+                                1));
+        StringJoiner summaryJoiner = new StringJoiner(System.lineSeparator());
+        summaryJoiner.setEmptyValue("");
+
+        String summaryText = cachedDevice.getCarConnectionSummary();
+        if (!TextUtils.isEmpty(summaryText)) {
+            summaryJoiner.add(summaryText);
+        }
+        // If hearing aids are connected, two battery statuses should be shown.
+        String pairDeviceSummary =
+                getBluetoothManager().getCachedDeviceManager().getHearingAidPairDeviceSummary(
+                        cachedDevice);
+        if (!TextUtils.isEmpty(pairDeviceSummary)) {
+            summaryJoiner.add(pairDeviceSummary);
+        }
+        preference.setTitle(cachedDevice.getName());
+        preference.setIcon(pair.first);
+        preference.getIcon().setTint(Themes.getAttrColor(getContext(), R.attr.iconColor));
+        preference.setSummary(summaryJoiner.toString());
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        getFragmentController().showDialog(
+                RemoteRenameDialogFragment.newInstance(getCachedDevice()),
+                RemoteRenameDialogFragment.TAG);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDevicePickerActivity.java b/src/com/android/car/settings/bluetooth/BluetoothDevicePickerActivity.java
new file mode 100644
index 0000000..db89075
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDevicePickerActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.bluetooth;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.common.BaseCarSettingsActivity;
+
+/**
+ * Displays a list of Bluetooth devices at the request of another application. When a user selects a
+ * device from the list, its details are returned to the requester. See {@link
+ * android.bluetooth.BluetoothDevicePicker}.
+ */
+public class BluetoothDevicePickerActivity extends BaseCarSettingsActivity {
+
+    @Nullable
+    @Override
+    protected Fragment getInitialFragment() {
+        return new BluetoothDevicePickerFragment();
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDevicePickerFragment.java b/src/com/android/car/settings/bluetooth/BluetoothDevicePickerFragment.java
new file mode 100644
index 0000000..658690d
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDevicePickerFragment.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.bluetooth;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/**
+ * Hosts {@link BluetoothDevicePickerPreferenceController} to display the list of Bluetooth
+ * devices. The progress bar is shown while this fragment is visible to indicate discovery or
+ * pairing progress.
+ */
+public class BluetoothDevicePickerFragment extends SettingsFragment {
+
+    private LocalBluetoothManager mManager;
+    private ProgressBar mProgressBar;
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.bluetooth_device_picker_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mManager = BluetoothUtils.getLocalBtManager(context);
+        if (mManager == null) {
+            goBack();
+            return;
+        }
+
+        use(BluetoothDevicePickerPreferenceController.class,
+                R.string.pk_bluetooth_device_picker).setLaunchIntent(requireActivity().getIntent());
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mProgressBar = requireActivity().findViewById(R.id.progress_bar);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mManager.setForegroundActivity(requireActivity());
+        mProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mManager.setForegroundActivity(null);
+        mProgressBar.setVisibility(View.GONE);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDevicePickerPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothDevicePickerPreferenceController.java
new file mode 100644
index 0000000..0c68eb5
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDevicePickerPreferenceController.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevicePicker;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+/**
+ * Displays a list of Bluetooth devices for the user to select. When a device is selected, a
+ * {@link BluetoothDevicePicker#ACTION_DEVICE_SELECTED} broadcast is sent containing {@link
+ * BluetoothDevice#EXTRA_DEVICE}.
+ *
+ * <p>This is useful to other application to obtain a device without needing to implement the UI.
+ * The activity hosting this controller should be launched with an intent as detailed in {@link
+ * BluetoothDevicePicker#ACTION_LAUNCH}. This controller will filter devices as specified by {@link
+ * BluetoothDevicePicker#EXTRA_FILTER_TYPE} and deliver the broadcast to the specified {@link
+ * BluetoothDevicePicker#EXTRA_LAUNCH_PACKAGE} {@link BluetoothDevicePicker#EXTRA_LAUNCH_CLASS}
+ * component.  If authentication is required ({@link BluetoothDevicePicker#EXTRA_NEED_AUTH}), this
+ * controller will initiate pairing with the device and send the selected broadcast once the device
+ * successfully pairs. If no device is selected and this controller is destroyed, a broadcast with
+ * a {@code null} {@link BluetoothDevice#EXTRA_DEVICE} is sent.
+ */
+public class BluetoothDevicePickerPreferenceController extends
+        BluetoothScanningDevicesGroupPreferenceController {
+
+    private static final Logger LOG = new Logger(BluetoothDevicePickerPreferenceController.class);
+
+    private BluetoothDeviceFilter.Filter mFilter;
+
+    private boolean mNeedAuth;
+    private String mLaunchPackage;
+    private String mLaunchClass;
+
+    private CachedBluetoothDevice mSelectedDevice;
+
+    public BluetoothDevicePickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /**
+     * Sets the intent with which {@link BluetoothDevicePickerActivity} was launched. The intent
+     * may contain {@link BluetoothDevicePicker} extras to customize the selection list and specify
+     * the destination of the selected device. See {@link BluetoothDevicePicker#ACTION_LAUNCH}.
+     */
+    public void setLaunchIntent(Intent intent) {
+        mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
+        mFilter = BluetoothDeviceFilter.getFilter(
+                intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
+                        BluetoothDevicePicker.FILTER_TYPE_ALL));
+        mLaunchPackage = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE);
+        mLaunchClass = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS);
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mFilter == null) {
+            throw new IllegalStateException("launch intent must be set");
+        }
+    }
+
+    @Override
+    protected BluetoothDeviceFilter.Filter getDeviceFilter() {
+        return mFilter;
+    }
+
+    @Override
+    protected void onDeviceClickedInternal(CachedBluetoothDevice cachedDevice) {
+        mSelectedDevice = cachedDevice;
+        BluetoothUtils.persistSelectedDeviceInPicker(getContext(), cachedDevice.getAddress());
+
+        if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED || !mNeedAuth) {
+            sendDevicePickedIntent(cachedDevice.getDevice());
+            getFragmentController().goBack();
+            return;
+        }
+
+        if (cachedDevice.startPairing()) {
+            LOG.d("startPairing");
+        } else {
+            BluetoothUtils.showError(getContext(), cachedDevice.getName(),
+                    R.string.bluetooth_pairing_error_message);
+            refreshUi();
+        }
+    }
+
+    @Override
+    protected void onStartInternal() {
+        super.onStartInternal();
+        mSelectedDevice = null;
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        super.onDestroyInternal();
+        if (mSelectedDevice == null) {
+            // Notify that no device was selected.
+            sendDevicePickedIntent(null);
+        }
+    }
+
+    @Override
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        super.onDeviceBondStateChanged(cachedDevice, bondState);
+        if (bondState == BluetoothDevice.BOND_BONDED && cachedDevice.equals(mSelectedDevice)) {
+            sendDevicePickedIntent(mSelectedDevice.getDevice());
+            getFragmentController().goBack();
+        }
+    }
+
+    private void sendDevicePickedIntent(BluetoothDevice device) {
+        LOG.d("sendDevicePickedIntent device: " + device + " package: " + mLaunchPackage
+                + " class: " + mLaunchClass);
+        Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        if (mLaunchPackage != null && mLaunchClass != null) {
+            intent.setClassName(mLaunchPackage, mLaunchClass);
+        }
+        getContext().sendBroadcast(intent);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/car/settings/bluetooth/BluetoothDevicePreference.java
new file mode 100644
index 0000000..4748e01
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDevicePreference.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.SystemProperties;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
+import com.android.car.apps.common.util.Themes;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+/**
+ * Preference which represents a specific {@link CachedBluetoothDevice}. The title, icon, and
+ * summary are kept in sync with the device when the preference is shown. When the device is busy,
+ * the preference is disabled. The equality and sort order of this preference is determined by the
+ * underlying cached device {@link CachedBluetoothDevice#equals(Object)} and {@link
+ * CachedBluetoothDevice#compareTo(CachedBluetoothDevice)}. If two devices are considered equal, the
+ * default preference sort ordering is used (see {@link #compareTo(Preference)}.
+ */
+public class BluetoothDevicePreference extends ButtonPreference {
+    private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
+            "persist.bluetooth.showdeviceswithoutnames";
+
+    private final CachedBluetoothDevice mCachedDevice;
+    private final boolean mShowDevicesWithoutNames;
+    private final CachedBluetoothDevice.Callback mDeviceCallback = this::refreshUi;
+
+    public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) {
+        super(context);
+        mCachedDevice = cachedDevice;
+        mShowDevicesWithoutNames = SystemProperties.getBoolean(
+                BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
+        // Hide action by default.
+        showAction(false);
+    }
+
+    /**
+     * Returns the {@link CachedBluetoothDevice} represented by this preference.
+     */
+    public CachedBluetoothDevice getCachedDevice() {
+        return mCachedDevice;
+    }
+
+    @Override
+    public void onAttached() {
+        super.onAttached();
+        mCachedDevice.registerCallback(mDeviceCallback);
+        refreshUi();
+    }
+
+    @Override
+    public void onDetached() {
+        super.onDetached();
+        mCachedDevice.unregisterCallback(mDeviceCallback);
+    }
+
+    private void refreshUi() {
+        setTitle(mCachedDevice.getName());
+        setSummary(mCachedDevice.getCarConnectionSummary());
+
+        final Pair<Drawable, String> pair = com.android.settingslib.bluetooth.Utils
+                .getBtClassDrawableWithDescription(getContext(), mCachedDevice);
+        if (pair.first != null) {
+            setIcon(pair.first);
+            getIcon().setTint(Themes.getAttrColor(getContext(), R.attr.iconColor));
+        }
+
+        setEnabled(!mCachedDevice.isBusy());
+        setVisible(mShowDevicesWithoutNames || mCachedDevice.hasHumanReadableName());
+
+        // Notify since the ordering may have changed.
+        notifyHierarchyChanged();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof BluetoothDevicePreference)) {
+            return false;
+        }
+        return mCachedDevice.equals(((BluetoothDevicePreference) o).mCachedDevice);
+    }
+
+    @Override
+    public int hashCode() {
+        return mCachedDevice.hashCode();
+    }
+
+    @Override
+    public int compareTo(@NonNull Preference another) {
+        if (!(another instanceof BluetoothDevicePreference)) {
+            // Rely on default sort.
+            return super.compareTo(another);
+        }
+
+        return mCachedDevice
+                .compareTo(((BluetoothDevicePreference) another).mCachedDevice);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceController.java
new file mode 100644
index 0000000..4fc12c3
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.UserManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+/**
+ * Encapsulates common functionality for all {@link BluetoothPreferenceController} instances
+ * which display state of a specific {@link CachedBluetoothDevice}. The controller will refresh
+ * the UI whenever the device properties change. The controller is not available to users with
+ * the {@link UserManager#DISALLOW_CONFIG_BLUETOOTH} restriction.
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller expects
+ *         to operate.
+ */
+public abstract class BluetoothDevicePreferenceController<V extends Preference> extends
+        BluetoothPreferenceController<V> {
+
+    private final CachedBluetoothDevice.Callback mDeviceCallback = this::refreshUi;
+    private CachedBluetoothDevice mCachedDevice;
+
+    public BluetoothDevicePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /**
+     * Sets the {@link CachedBluetoothDevice} which the controller represents. The device must be
+     * set or an {@link IllegalStateException} will be thrown when this controller begins its
+     * lifecycle.
+     */
+    public void setCachedDevice(CachedBluetoothDevice device) {
+        mCachedDevice = device;
+    }
+
+    /**
+     * Returns the {@link CachedBluetoothDevice} represented by this controller.
+     */
+    public CachedBluetoothDevice getCachedDevice() {
+        return mCachedDevice;
+    }
+
+
+    @Override
+    protected void checkInitialized() {
+        if (mCachedDevice == null) {
+            throw new IllegalStateException("Must be initialized with a CachedBluetoothDevice");
+        }
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        int availabilityStatus = super.getAvailabilityStatus();
+        if (availabilityStatus == AVAILABLE) {
+            return getCarUserManagerHelper().isCurrentProcessUserHasRestriction(
+                    DISALLOW_CONFIG_BLUETOOTH) ? DISABLED_FOR_USER : AVAILABLE;
+        }
+        return availabilityStatus;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        super.onStartInternal();
+        mCachedDevice.registerCallback(mDeviceCallback);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        super.onStopInternal();
+        mCachedDevice.unregisterCallback(mDeviceCallback);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilePreference.java b/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilePreference.java
new file mode 100644
index 0000000..a6ac320
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilePreference.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
+
+import android.content.Context;
+
+import androidx.preference.SwitchPreference;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+import com.android.settingslib.bluetooth.PanProfile;
+
+/**
+ * Preference that represents a {@link LocalBluetoothProfile} for a {@link CachedBluetoothDevice}.
+ */
+public class BluetoothDeviceProfilePreference extends SwitchPreference {
+
+    private final LocalBluetoothProfile mProfile;
+    private final CachedBluetoothDevice mCachedDevice;
+    private final CachedBluetoothDevice.Callback mDeviceCallback = this::refreshUi;
+
+    public BluetoothDeviceProfilePreference(Context context, LocalBluetoothProfile profile,
+            CachedBluetoothDevice cachedDevice) {
+        super(context);
+        mProfile = profile;
+        mCachedDevice = cachedDevice;
+        setKey(profile.toString());
+        setTitle(profile.getNameResource(cachedDevice.getDevice()));
+    }
+
+    /**
+     * Returns the {@link LocalBluetoothProfile} represented by this preference.
+     */
+    public LocalBluetoothProfile getProfile() {
+        return mProfile;
+    }
+
+    /**
+     * Returns the {@link CachedBluetoothDevice} used to construct this preference.
+     */
+    public CachedBluetoothDevice getCachedDevice() {
+        return mCachedDevice;
+    }
+
+    @Override
+    public void onAttached() {
+        super.onAttached();
+        mCachedDevice.registerCallback(mDeviceCallback);
+        refreshUi();
+    }
+
+    @Override
+    public void onDetached() {
+        super.onDetached();
+        mCachedDevice.unregisterCallback(mDeviceCallback);
+    }
+
+    private void refreshUi() {
+        setEnabled(!mCachedDevice.isBusy());
+        if (mProfile instanceof PanProfile) {
+            setChecked(
+                    mProfile.getConnectionStatus(mCachedDevice.getDevice()) == STATE_CONNECTED);
+        } else {
+            setChecked(mProfile.isPreferred(mCachedDevice.getDevice()));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilesPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilesPreferenceController.java
new file mode 100644
index 0000000..9df8054
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilesPreferenceController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+
+/**
+ * Displays toggles for Bluetooth profiles supported by a device. Toggling a profile on will set it
+ * as preferred and attempt a connection. Toggling a profile off will disconnect the profile. If no
+ * profiles are supported, the preference is hidden.
+ */
+public class BluetoothDeviceProfilesPreferenceController extends
+        BluetoothDevicePreferenceController<PreferenceGroup> {
+
+    private final Preference.OnPreferenceChangeListener mProfileChangeListener =
+            (preference, newValue) -> {
+                boolean isChecked = (boolean) newValue;
+                BluetoothDeviceProfilePreference profilePref =
+                        (BluetoothDeviceProfilePreference) preference;
+                LocalBluetoothProfile profile = profilePref.getProfile();
+                profile.setPreferred(profilePref.getCachedDevice().getDevice(), isChecked);
+                if (isChecked) {
+                    getCachedDevice().connectProfile(profile);
+                } else {
+                    getCachedDevice().disconnect(profile);
+                }
+                return true;
+            };
+
+    public BluetoothDeviceProfilesPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        for (LocalBluetoothProfile profile : getCachedDevice().getProfiles()) {
+            Preference profilePref = preferenceGroup.findPreference(profile.toString());
+            if (profilePref == null) {
+                profilePref = new BluetoothDeviceProfilePreference(getContext(), profile,
+                        getCachedDevice());
+                profilePref.setOnPreferenceChangeListener(mProfileChangeListener);
+                preferenceGroup.addPreference(profilePref);
+            }
+        }
+        for (LocalBluetoothProfile removedProfile : getCachedDevice().getRemovedProfiles()) {
+            Preference prefToRemove = preferenceGroup.findPreference(removedProfile.toString());
+            if (prefToRemove != null) {
+                preferenceGroup.removePreference(prefToRemove);
+            }
+        }
+        preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDevicesGroupPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothDevicesGroupPreferenceController.java
new file mode 100644
index 0000000..8548821
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDevicesGroupPreferenceController.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.annotation.CallSuper;
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages a group of Bluetooth devices by adding preferences for devices that pass a subclass
+ * defined filter and removing preferences for devices that no longer pass. Subclasses are
+ * dispatched click events on individual preferences to customize the behavior.
+ *
+ * <p>Note: {@link #refreshUi()} is called whenever a device is added or removed with {@link
+ * #onDeviceAdded(CachedBluetoothDevice)} or {@link #onDeviceDeleted(CachedBluetoothDevice)}.
+ * Subclasses should listen to state changes (and possibly override additional {@link
+ * BluetoothCallback} methods) and call {@link #refreshUi()} for changes which affect their
+ * implementation of {@link #getDeviceFilter()}.
+ */
+public abstract class BluetoothDevicesGroupPreferenceController extends
+        BluetoothPreferenceController<PreferenceGroup> {
+
+    private final Map<CachedBluetoothDevice, BluetoothDevicePreference> mPreferenceMap =
+            new HashMap<>();
+    private final Preference.OnPreferenceClickListener mDevicePreferenceClickListener =
+            preference -> {
+                onDeviceClicked(((BluetoothDevicePreference) preference).getCachedDevice());
+                return true;
+            };
+
+    public BluetoothDevicesGroupPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /**
+     * Returns a filter for which devices should be included in the group. Devices that do not
+     * pass the filter will not be added. Added devices that no longer pass the filter will be
+     * removed.
+     */
+    protected abstract BluetoothDeviceFilter.Filter getDeviceFilter();
+
+    /**
+     * Returns a newly created {@link BluetoothDevicePreference} for the given {@link
+     * CachedBluetoothDevice}. Subclasses may override this method to customize how devices are
+     * represented in the group.
+     */
+    protected BluetoothDevicePreference createDevicePreference(CachedBluetoothDevice cachedDevice) {
+        return new BluetoothDevicePreference(getContext(), cachedDevice);
+    }
+
+    /**
+     * Called when a preference in the group is clicked.
+     *
+     * @param cachedDevice the device represented by the clicked preference.
+     */
+    protected abstract void onDeviceClicked(CachedBluetoothDevice cachedDevice);
+
+    /**
+     * Returns a mapping of all {@link CachedBluetoothDevice} instances represented by this group
+     * and their associated preferences.
+     */
+    protected Map<CachedBluetoothDevice, BluetoothDevicePreference> getPreferenceMap() {
+        return mPreferenceMap;
+    }
+
+    @Override
+    @CallSuper
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        Collection<CachedBluetoothDevice> cachedDevices =
+                getBluetoothManager().getCachedDeviceManager().getCachedDevicesCopy();
+
+        Set<CachedBluetoothDevice> devicesToRemove = new HashSet<>(mPreferenceMap.keySet());
+        devicesToRemove.removeAll(cachedDevices);
+        for (CachedBluetoothDevice deviceToRemove : devicesToRemove) {
+            removePreference(deviceToRemove);
+        }
+
+        for (CachedBluetoothDevice cachedDevice : cachedDevices) {
+            if (getDeviceFilter().matches(cachedDevice.getDevice())) {
+                addPreference(cachedDevice);
+            } else {
+                removePreference(cachedDevice);
+            }
+        }
+
+        preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0);
+    }
+
+    @Override
+    public final void onBluetoothStateChanged(int bluetoothState) {
+        super.onBluetoothStateChanged(bluetoothState);
+        if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
+            // Cleanup the UI so that we don't have stale representations when the adapter turns
+            // on again. This can happen if Bluetooth crashes and restarts.
+            getPreference().removeAll();
+            mPreferenceMap.clear();
+        }
+    }
+
+    @Override
+    public final void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+        refreshUi();
+    }
+
+    @Override
+    public final void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+        refreshUi();
+    }
+
+    private void addPreference(CachedBluetoothDevice cachedDevice) {
+        if (!mPreferenceMap.containsKey(cachedDevice)) {
+            BluetoothDevicePreference devicePreference = createDevicePreference(cachedDevice);
+            devicePreference.setOnPreferenceClickListener(mDevicePreferenceClickListener);
+            mPreferenceMap.put(cachedDevice, devicePreference);
+            getPreference().addPreference(devicePreference);
+        }
+    }
+
+    private void removePreference(CachedBluetoothDevice cachedDevice) {
+        if (mPreferenceMap.containsKey(cachedDevice)) {
+            getPreference().removePreference(mPreferenceMap.get(cachedDevice));
+            mPreferenceMap.remove(cachedDevice);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothDisconnectConfirmDialogFragment.java b/src/com/android/car/settings/bluetooth/BluetoothDisconnectConfirmDialogFragment.java
new file mode 100644
index 0000000..c1caf61
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothDisconnectConfirmDialogFragment.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+
+import com.android.car.settings.R;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/**
+ * Displays a dialog which prompts the user to confirm disconnecting from a remote Bluetooth device.
+ */
+public class BluetoothDisconnectConfirmDialogFragment extends DialogFragment {
+
+    private static final String KEY_DEVICE_ADDRESS = "device_address";
+
+    private final CachedBluetoothDevice.Callback mDeviceCallback = this::dismissIfNotConnected;
+    private CachedBluetoothDevice mCachedDevice;
+
+    /**
+     * Returns a new {@link BluetoothDisconnectConfirmDialogFragment} for the given {@code device}.
+     */
+    public static BluetoothDisconnectConfirmDialogFragment newInstance(
+            CachedBluetoothDevice device) {
+        Bundle args = new Bundle();
+        args.putString(KEY_DEVICE_ADDRESS, device.getAddress());
+        BluetoothDisconnectConfirmDialogFragment fragment =
+                new BluetoothDisconnectConfirmDialogFragment();
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        String deviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
+        LocalBluetoothManager manager = BluetoothUtils.getLocalBtManager(context);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+                deviceAddress);
+        mCachedDevice = manager.getCachedDeviceManager().findDevice(device);
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        Context context = requireContext();
+        String name = mCachedDevice.getName();
+        if (TextUtils.isEmpty(name)) {
+            name = context.getString(R.string.bluetooth_device);
+        }
+        String title = context.getString(R.string.bluetooth_disconnect_title);
+        String message = context.getString(R.string.bluetooth_disconnect_all_profiles, name);
+
+        return new AlertDialog.Builder(context)
+                .setTitle(title)
+                .setMessage(message)
+                .setPositiveButton(android.R.string.ok,
+                        (dialog, which) -> mCachedDevice.disconnect())
+                .setNegativeButton(android.R.string.cancel, /* listener= */ null)
+                .create();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mCachedDevice.registerCallback(mDeviceCallback);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mCachedDevice.unregisterCallback(mDeviceCallback);
+    }
+
+    private void dismissIfNotConnected() {
+        // This handles the case where the dialog is showing and the connection is broken via UI
+        // on the remote device. It does not cover the case of the device disconnecting while the
+        // fragment is starting because we cannot begin another transaction for dismiss while in
+        // a transaction to show. That case, however, should be extremely rare, and the action
+        // taken on the dialog will have no effect.
+        if (!mCachedDevice.isConnected() && getDialog().isShowing()) {
+            dismiss();
+        }
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothEntryPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothEntryPreferenceController.java
new file mode 100644
index 0000000..4136f95
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothEntryPreferenceController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_BLUETOOTH;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller which determines if the top level entry into Bluetooth settings should be displayed
+ * based on device capabilities and user restrictions.
+ */
+public class BluetoothEntryPreferenceController extends PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public BluetoothEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (!getContext().getPackageManager().hasSystemFeature(FEATURE_BLUETOOTH)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        return mCarUserManagerHelper.isCurrentProcessUserHasRestriction(DISALLOW_BLUETOOTH)
+                ? DISABLED_FOR_USER : AVAILABLE;
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothNamePreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothNamePreferenceController.java
new file mode 100644
index 0000000..efc6387
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothNamePreferenceController.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Displays the name of the local Bluetooth adapter. When the associated preference is clicked, a
+ * dialog is shown to allow the user to update the adapter name. If the user has the {@link
+ * DISALLOW_CONFIG_BLUETOOTH} restriction, interaction with the preference is disabled.
+ */
+public class BluetoothNamePreferenceController extends BluetoothPreferenceController<Preference> {
+
+    private final IntentFilter mIntentFilter = new IntentFilter(
+            BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshUi();
+        }
+    };
+
+    public BluetoothNamePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        super.onStartInternal();
+        getContext().registerReceiver(mReceiver, mIntentFilter);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        super.onStopInternal();
+        getContext().unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSelectable(!getCarUserManagerHelper().isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH));
+        preference.setSummary(BluetoothAdapter.getDefaultAdapter().getName());
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        getFragmentController().showDialog(new LocalRenameDialogFragment(),
+                LocalRenameDialogFragment.TAG);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/car/settings/bluetooth/BluetoothPairingDialog.java
index 740c382..c70af2a 100644
--- a/src/com/android/car/settings/bluetooth/BluetoothPairingDialog.java
+++ b/src/com/android/car/settings/bluetooth/BluetoothPairingDialog.java
@@ -16,7 +16,6 @@
 
 package com.android.car.settings.bluetooth;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
@@ -24,7 +23,9 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
-import android.support.annotation.VisibleForTesting;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 /**
  * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
diff --git a/src/com/android/car/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/car/settings/bluetooth/BluetoothPairingRequest.java
index dd69c32..2115998 100644
--- a/src/com/android/car/settings/bluetooth/BluetoothPairingRequest.java
+++ b/src/com/android/car/settings/bluetooth/BluetoothPairingRequest.java
@@ -20,7 +20,6 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.os.PowerManager;
 import android.os.UserHandle;
 
@@ -32,31 +31,31 @@
  */
 public final class BluetoothPairingRequest extends BroadcastReceiver {
 
-  @Override
-  public void onReceive(Context context, Intent intent) {
-    String action = intent.getAction();
-    if (!action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
-      return;
-    }
-    // convert broadcast intent into activity intent (same action string)
-    Intent pairingIntent = BluetoothPairingService.getPairingDialogIntent(context, intent);
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (!action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
+            return;
+        }
+        // convert broadcast intent into activity intent (same action string)
+        Intent pairingIntent = BluetoothPairingService.getPairingDialogIntent(context, intent);
 
-    PowerManager powerManager =
-        (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-    BluetoothDevice device =
-        intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-    String deviceAddress = device != null ? device.getAddress() : null;
-    String deviceName = device != null ? device.getName() : null;
-    boolean shouldShowDialog = BluetoothUtils.shouldShowDialogInForeground(
-        context, deviceAddress, deviceName);
-    if (powerManager.isInteractive() && shouldShowDialog) {
-      // Since the screen is on and the BT-related activity is in the foreground,
-      // just open the dialog
-      context.startActivityAsUser(pairingIntent, UserHandle.CURRENT);
-    } else {
-      // Put up a notification that leads to the dialog
-      intent.setClass(context, BluetoothPairingService.class);
-      context.startServiceAsUser(intent, UserHandle.CURRENT);
+        PowerManager powerManager =
+                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        BluetoothDevice device =
+                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+        String deviceAddress = device != null ? device.getAddress() : null;
+        String deviceName = device != null ? device.getName() : null;
+        boolean shouldShowDialog = BluetoothUtils.shouldShowDialogInForeground(
+                context, deviceAddress, deviceName);
+        if (powerManager.isInteractive() && shouldShowDialog) {
+            // Since the screen is on and the BT-related activity is in the foreground,
+            // just open the dialog
+            context.startActivityAsUser(pairingIntent, UserHandle.CURRENT);
+        } else {
+            // Put up a notification that leads to the dialog
+            intent.setClass(context, BluetoothPairingService.class);
+            context.startServiceAsUser(intent, UserHandle.CURRENT);
+        }
     }
-  }
 }
diff --git a/src/com/android/car/settings/bluetooth/BluetoothPairingSelectionFragment.java b/src/com/android/car/settings/bluetooth/BluetoothPairingSelectionFragment.java
new file mode 100644
index 0000000..4f6af3e
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothPairingSelectionFragment.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/**
+ * Page which scans for Bluetooth devices so that a user can select a new device to pair. When a
+ * device pairs, this page will finish.
+ */
+public class BluetoothPairingSelectionFragment extends SettingsFragment {
+
+    private final BluetoothCallback mCallback = new BluetoothCallback() {
+        @Override
+        public void onBluetoothStateChanged(int bluetoothState) {
+        }
+
+        @Override
+        public void onScanningStateChanged(boolean started) {
+        }
+
+        @Override
+        public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+        }
+
+        @Override
+        public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+        }
+
+        @Override
+        public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+            if (bondState == BluetoothDevice.BOND_BONDED) {
+                // We are in a dispatch loop from event manager to all listeners. goBack will pop
+                // immediately, stopping this fragment causing an unregister from the event manager
+                // and a ConcurrentModificationException. Wait until the dispatch is done to go
+                // back.
+                requireActivity().getMainThreadHandler().post(
+                        BluetoothPairingSelectionFragment.this::goBack);
+            }
+        }
+
+        @Override
+        public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        }
+
+        @Override
+        public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice,
+                int bluetoothProfile) {
+        }
+
+        @Override
+        public void onAudioModeChanged() {
+        }
+    };
+
+    private LocalBluetoothManager mManager;
+    private ProgressBar mProgressBar;
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.bluetooth_pairing_selection_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mManager = BluetoothUtils.getLocalBtManager(context);
+        if (mManager == null) {
+            goBack();
+        }
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mProgressBar = requireActivity().findViewById(R.id.progress_bar);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mManager.setForegroundActivity(requireActivity());
+        mManager.getEventManager().registerCallback(mCallback);
+        mProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mManager.setForegroundActivity(null);
+        mManager.getEventManager().unregisterCallback(mCallback);
+        mProgressBar.setVisibility(View.GONE);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothPairingService.java b/src/com/android/car/settings/bluetooth/BluetoothPairingService.java
index 848cbba..156a483 100644
--- a/src/com/android/car/settings/bluetooth/BluetoothPairingService.java
+++ b/src/com/android/car/settings/bluetooth/BluetoothPairingService.java
@@ -78,7 +78,8 @@
             if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
                         BluetoothDevice.ERROR);
-                if ((bondState != BluetoothDevice.BOND_NONE) && (bondState != BluetoothDevice.BOND_BONDED)) {
+                if ((bondState != BluetoothDevice.BOND_NONE) && (bondState
+                        != BluetoothDevice.BOND_BONDED)) {
                     return;
                 }
             } else if (action.equals(ACTION_DISMISS_PAIRING)) {
@@ -98,13 +99,13 @@
 
     @Override
     public void onCreate() {
-      NotificationManager mgr = (NotificationManager)this
-         .getSystemService(Context.NOTIFICATION_SERVICE);
-      NotificationChannel notificationChannel = new NotificationChannel(
-         BLUETOOTH_NOTIFICATION_CHANNEL,
-         this.getString(R.string.bluetooth),
-         NotificationManager.IMPORTANCE_HIGH);
-      mgr.createNotificationChannel(notificationChannel);
+        NotificationManager mgr = (NotificationManager) this
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+        NotificationChannel notificationChannel = new NotificationChannel(
+                BLUETOOTH_NOTIFICATION_CHANNEL,
+                this.getString(R.string.bluetooth_settings_title),
+                NotificationManager.IMPORTANCE_HIGH);
+        mgr.createNotificationChannel(notificationChannel);
     }
 
     @Override
@@ -117,7 +118,7 @@
 
         Resources res = getResources();
         Notification.Builder builder = new Notification.Builder(this,
-            BLUETOOTH_NOTIFICATION_CHANNEL)
+                BLUETOOTH_NOTIFICATION_CHANNEL)
                 .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
                 .setTicker(res.getString(R.string.bluetooth_notif_ticker))
                 .setLocalOnly(true);
@@ -139,7 +140,8 @@
         String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
         if (TextUtils.isEmpty(name)) {
             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            name = device != null ? device.getAliasName() : res.getString(android.R.string.unknownName);
+            name = device != null ? device.getAliasName() : res.getString(
+                    android.R.string.unknownName);
         }
 
         LOG.d("Show pairing notification for " + mDevice.getAddress() + " (" + name + ")");
diff --git a/src/com/android/car/settings/bluetooth/BluetoothPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothPreferenceController.java
new file mode 100644
index 0000000..6c9ce00
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothPreferenceController.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.os.UserManager.DISALLOW_BLUETOOTH;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.annotation.CallSuper;
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/**
+ * Abstract {@link PreferenceController} that listens to {@link BluetoothCallback} events and
+ * exposes the interface methods for override. It implements a default
+ * {@link #getAvailabilityStatus()} which is {@link #AVAILABLE} when the  system supports
+ * Bluetooth, the current user is not restricted by {@link DISALLOW_BLUETOOTH}, and the default
+ * Bluetooth adapter is enabled.
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller expects
+ *         to operate.
+ */
+public abstract class BluetoothPreferenceController<V extends Preference> extends
+        PreferenceController<V> implements BluetoothCallback {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final LocalBluetoothManager mBluetoothManager;
+
+    public BluetoothPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        mBluetoothManager = BluetoothUtils.getLocalBtManager(context);
+    }
+
+    /** Returns a {@link CarUserManagerHelper} constructed from the controller context. */
+    protected final CarUserManagerHelper getCarUserManagerHelper() {
+        return mCarUserManagerHelper;
+    }
+
+    /**
+     * Returns a {@link LocalBluetoothManager} retrieved with the controller context. This is
+     * not {@code null} unless {@link #getAvailabilityStatus()} returns
+     * {@link #UNSUPPORTED_ON_DEVICE}.
+     */
+    protected final LocalBluetoothManager getBluetoothManager() {
+        return mBluetoothManager;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        if (mCarUserManagerHelper.isCurrentProcessUserHasRestriction(DISALLOW_BLUETOOTH)) {
+            return DISABLED_FOR_USER;
+        }
+        return BluetoothAdapter.getDefaultAdapter().isEnabled() ? AVAILABLE
+                : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    @Override
+    @CallSuper
+    protected void onStartInternal() {
+        mBluetoothManager.getEventManager().registerCallback(this);
+    }
+
+    @Override
+    @CallSuper
+    protected void onStopInternal() {
+        mBluetoothManager.getEventManager().unregisterCallback(this);
+    }
+
+    @Override
+    @CallSuper
+    public void onBluetoothStateChanged(int bluetoothState) {
+        refreshUi();
+    }
+
+    @Override
+    public void onScanningStateChanged(boolean started) {
+    }
+
+    @Override
+    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+    }
+
+    @Override
+    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+    }
+
+    @Override
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+    }
+
+    @Override
+    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+    }
+
+    @Override
+    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+    }
+
+    @Override
+    public void onAudioModeChanged() {
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothProfileLineItem.java b/src/com/android/car/settings/bluetooth/BluetoothProfileLineItem.java
deleted file mode 100644
index 51a1119..0000000
--- a/src/com/android/car/settings/bluetooth/BluetoothProfileLineItem.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.bluetooth;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.view.View;
-import android.widget.CheckBox;
-
-import com.android.car.list.CheckBoxLineItem;
-import com.android.car.settings.R;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
-import com.android.settingslib.bluetooth.PanProfile;
-
-/**
- * Represents a line item for a Bluetooth mProfile.
- */
-public class BluetoothProfileLineItem extends CheckBoxLineItem {
-    private final LocalBluetoothProfile mProfile;
-    private final CachedBluetoothDevice mCachedDevice;
-    private CheckboxLineItemViewHolder mViewHolder;
-    private DataChangedListener mDataChangedListener;
-    private final Context mContext;
-
-    public interface DataChangedListener {
-        void onDataChanged();
-    }
-
-    public BluetoothProfileLineItem(Context context, LocalBluetoothProfile profile,
-            CachedBluetoothDevice cachedBluetoothDevice, DataChangedListener listener) {
-        super(context.getText(profile.getNameResource(cachedBluetoothDevice.getDevice())));
-        mContext = context;
-        mCachedDevice = cachedBluetoothDevice;
-        mProfile = profile;
-        mDataChangedListener = listener;
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (((CheckBox) view.findViewById(R.id.checkbox)).isChecked()) {
-            mCachedDevice.disconnect(mProfile);
-            mProfile.setPreferred(mCachedDevice.getDevice(), false);
-        } else if (mProfile.isPreferred(mCachedDevice.getDevice())) {
-            if (mProfile instanceof PanProfile) {
-                mCachedDevice.connectProfile(mProfile);
-            } else {
-                mProfile.setPreferred(mCachedDevice.getDevice(), false);
-            }
-        } else {
-            mProfile.setPreferred(mCachedDevice.getDevice(), true);
-            mCachedDevice.connectProfile(mProfile);
-        }
-        mDataChangedListener.onDataChanged();
-    }
-
-    @Override
-    public boolean isExpandable() {
-        return false;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return true;
-    }
-
-    @Override
-    public void bindViewHolder(CheckboxLineItemViewHolder holder) {
-        super.bindViewHolder(holder);
-        mViewHolder = holder;
-    }
-
-    @Override
-    public boolean isChecked() {
-        BluetoothDevice device = mCachedDevice.getDevice();
-        if (mProfile instanceof PanProfile) {
-            return mProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
-        } else {
-            return mProfile.isPreferred(device);
-        }
-    }
-}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothRenameDialogFragment.java b/src/com/android/car/settings/bluetooth/BluetoothRenameDialogFragment.java
new file mode 100644
index 0000000..cf5da80
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothRenameDialogFragment.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.fragment.app.DialogFragment;
+
+import com.android.car.settings.R;
+
+import java.util.Objects;
+
+/** Dialog fragment for renaming a Bluetooth device. */
+public abstract class BluetoothRenameDialogFragment extends DialogFragment implements TextWatcher,
+        TextView.OnEditorActionListener {
+
+    // Keys to save the edited name and edit status for restoring after configuration change.
+    private static final String KEY_NAME = "device_name";
+
+    private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248;
+
+    private AlertDialog mAlertDialog;
+    private EditText mDeviceNameView;
+    private Button mRenameButton;
+
+    /** Returns the title to use for the dialog. */
+    @StringRes
+    protected abstract int getDialogTitle();
+
+    /** Returns the current name used for this device or {@code null} if a name is not available. */
+    @Nullable
+    protected abstract String getDeviceName();
+
+    /**
+     * Set the device to the given name.
+     *
+     * @param deviceName the name to use.
+     */
+    protected abstract void setDeviceName(String deviceName);
+
+    @Override
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        String deviceName = getDeviceName();
+        if (savedInstanceState != null) {
+            deviceName = savedInstanceState.getString(KEY_NAME, deviceName);
+        }
+        AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity())
+                .setTitle(getDialogTitle())
+                .setView(createDialogView(deviceName))
+                .setPositiveButton(R.string.bluetooth_rename_button,
+                        (dialog, which) -> setDeviceName(
+                                mDeviceNameView.getText().toString().trim()))
+                .setNegativeButton(android.R.string.cancel, /* listener= */ null);
+        mAlertDialog = builder.create();
+        mAlertDialog.setOnShowListener(d -> {
+            if (mDeviceNameView.requestFocus()) {
+                InputMethodManager imm = (InputMethodManager) requireContext().getSystemService(
+                        Context.INPUT_METHOD_SERVICE);
+                if (imm != null) {
+                    imm.showSoftInput(mDeviceNameView, InputMethodManager.SHOW_IMPLICIT);
+                }
+            }
+        });
+
+        return mAlertDialog;
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putString(KEY_NAME, mDeviceNameView.getText().toString());
+    }
+
+    private View createDialogView(String deviceName) {
+        final LayoutInflater layoutInflater = (LayoutInflater) requireActivity().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        // TODO: use dialog layout defined in preference theme.
+        View view = layoutInflater.inflate(R.layout.preference_dialog_edittext, /* root= */ null);
+        mDeviceNameView = view.findViewById(android.R.id.edit);
+        mDeviceNameView.setFilters(new InputFilter[]{
+                new Utf8ByteLengthFilter(BLUETOOTH_NAME_MAX_LENGTH_BYTES)
+        });
+        mDeviceNameView.setText(deviceName); // Set initial value before adding listener.
+        if (!TextUtils.isEmpty(deviceName)) {
+            mDeviceNameView.setSelection(deviceName.length());
+        }
+        mDeviceNameView.addTextChangedListener(this);
+        mDeviceNameView.setOnEditorActionListener(this);
+        mDeviceNameView.setRawInputType(InputType.TYPE_CLASS_TEXT);
+        mDeviceNameView.setImeOptions(EditorInfo.IME_ACTION_DONE);
+
+        return view;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mRenameButton = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+        refreshRenameButton();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mAlertDialog = null;
+        mDeviceNameView = null;
+        mRenameButton = null;
+    }
+
+    /** Refreshes the displayed device name with the latest value from {@link #getDeviceName()}. */
+    protected void updateDeviceName() {
+        String name = getDeviceName();
+        if (name != null) {
+            mDeviceNameView.setText(name);
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        refreshRenameButton();
+    }
+
+    @Override
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (actionId == EditorInfo.IME_ACTION_DONE) {
+            String editedName = getEditedName();
+            if (TextUtils.isEmpty(editedName)) {
+                return false;
+            }
+            setDeviceName(editedName);
+            if (mAlertDialog != null && mAlertDialog.isShowing()) {
+                mAlertDialog.dismiss();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void refreshRenameButton() {
+        String editedName = getEditedName();
+        mRenameButton.setEnabled(
+                !TextUtils.isEmpty(editedName) && !Objects.equals(editedName, getDeviceName()));
+    }
+
+    private String getEditedName() {
+        return mDeviceNameView.getText().toString().trim();
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothScanningDevicesGroupPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothScanningDevicesGroupPreferenceController.java
new file mode 100644
index 0000000..f39598a
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothScanningDevicesGroupPreferenceController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.bluetooth;
+
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+/**
+ * Controller which sets the Bluetooth adapter to discovery mode and begins scanning for
+ * discoverable devices for as long as the preference group is shown. Discovery
+ * and scanning are halted while any device is pairing. Users with the {@link
+ * DISALLOW_CONFIG_BLUETOOTH} restriction cannot scan for devices, so only cached devices will be
+ * shown.
+ */
+public abstract class BluetoothScanningDevicesGroupPreferenceController extends
+        BluetoothDevicesGroupPreferenceController {
+
+    private static final Logger LOG = new Logger(
+            BluetoothScanningDevicesGroupPreferenceController.class);
+
+    private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+    private final AlwaysDiscoverable mAlwaysDiscoverable;
+    private boolean mIsScanningEnabled;
+
+    public BluetoothScanningDevicesGroupPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAlwaysDiscoverable = new AlwaysDiscoverable(context, mBluetoothAdapter);
+    }
+
+    @Override
+    protected final void onDeviceClicked(CachedBluetoothDevice cachedDevice) {
+        LOG.d("onDeviceClicked: " + cachedDevice);
+        disableScanning();
+        onDeviceClickedInternal(cachedDevice);
+    }
+
+    /**
+     * Called when the user selects a device in the group.
+     *
+     * @param cachedDevice the device represented by the selected preference.
+     */
+    protected abstract void onDeviceClickedInternal(CachedBluetoothDevice cachedDevice);
+
+    @Override
+    protected void onStopInternal() {
+        super.onStopInternal();
+        disableScanning();
+        getBluetoothManager().getCachedDeviceManager().clearNonBondedDevices();
+        getPreferenceMap().clear();
+        getPreference().removeAll();
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        super.updateState(preferenceGroup);
+        if (shouldEnableScanning()) {
+            enableScanning();
+        } else {
+            disableScanning();
+        }
+    }
+
+    private boolean shouldEnableScanning() {
+        for (CachedBluetoothDevice device : getPreferenceMap().keySet()) {
+            if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
+                return false;
+            }
+        }
+        // Users who cannot configure Bluetooth cannot scan.
+        return !getCarUserManagerHelper().isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH);
+    }
+
+    /**
+     * Starts scanning for devices which will be displayed in the group for a user to select.
+     * Calls are idempotent.
+     */
+    private void enableScanning() {
+        mIsScanningEnabled = true;
+        if (!mBluetoothAdapter.isDiscovering()) {
+            mBluetoothAdapter.startDiscovery();
+        }
+        mAlwaysDiscoverable.start();
+        getPreference().setEnabled(true);
+    }
+
+    /** Stops scanning for devices and disables interaction. Calls are idempotent. */
+    private void disableScanning() {
+        mIsScanningEnabled = false;
+        getPreference().setEnabled(false);
+        mAlwaysDiscoverable.stop();
+        if (mBluetoothAdapter.isDiscovering()) {
+            mBluetoothAdapter.cancelDiscovery();
+        }
+    }
+
+    @Override
+    public void onScanningStateChanged(boolean started) {
+        LOG.d("onScanningStateChanged started: " + started + " mIsScanningEnabled: "
+                + mIsScanningEnabled);
+        if (!started && mIsScanningEnabled) {
+            enableScanning();
+        }
+    }
+
+    @Override
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        LOG.d("onDeviceBondStateChanged device: " + cachedDevice + " state: " + bondState);
+        refreshUi();
+    }
+
+    /**
+     * Helper class to keep the {@link BluetoothAdapter} in discoverable mode indefinitely. By
+     * default, setting the scan mode to BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE will
+     * timeout, but for pairing, we want to keep the device discoverable as long as the page is
+     * scanning.
+     */
+    private static final class AlwaysDiscoverable extends BroadcastReceiver {
+
+        private final Context mContext;
+        private final BluetoothAdapter mAdapter;
+        private final IntentFilter mIntentFilter = new IntentFilter(
+                BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
+
+        private boolean mStarted;
+
+        AlwaysDiscoverable(Context context, BluetoothAdapter adapter) {
+            mContext = context;
+            mAdapter = adapter;
+        }
+
+        /**
+         * Sets the adapter scan mode to
+         * {@link BluetoothAdapter#SCAN_MODE_CONNECTABLE_DISCOVERABLE}. {@link #start()} calls
+         * should have a matching calls to {@link #stop()} when discover mode is no longer needed.
+         */
+        void start() {
+            if (mStarted) {
+                return;
+            }
+            mContext.registerReceiver(this, mIntentFilter);
+            mStarted = true;
+            setDiscoverable();
+        }
+
+        void stop() {
+            if (!mStarted) {
+                return;
+            }
+            mContext.unregisterReceiver(this);
+            mStarted = false;
+            mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            setDiscoverable();
+        }
+
+        private void setDiscoverable() {
+            if (mAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+                mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+            }
+        }
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothSettingsFragment.java b/src/com/android/car/settings/bluetooth/BluetoothSettingsFragment.java
index f0fde8e..c963bd8 100644
--- a/src/com/android/car/settings/bluetooth/BluetoothSettingsFragment.java
+++ b/src/com/android/car/settings/bluetooth/BluetoothSettingsFragment.java
@@ -13,226 +13,131 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.car.settings.bluetooth;
 
+import static android.os.UserManager.DISALLOW_BLUETOOTH;
+
 import android.bluetooth.BluetoothAdapter;
+import android.car.userlib.CarUserManagerHelper;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.view.View;
-import android.widget.ProgressBar;
+import android.widget.CompoundButton;
 import android.widget.Switch;
-import android.widget.TextView;
-import android.widget.ViewSwitcher;
 
-import androidx.car.widget.PagedListView;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.Logger;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.car.settings.common.SettingsFragment;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 /**
- * Hosts Bluetooth related preferences.
+ * Main page for Bluetooth settings. It manages the power switch for the Bluetooth adapter. It also
+ * displays paired devices and the entry point for device pairing.
  */
-public class BluetoothSettingsFragment extends BaseFragment implements BluetoothCallback {
-    private static final Logger LOG = new Logger(BluetoothSettingsFragment.class);
+public class BluetoothSettingsFragment extends SettingsFragment {
 
-    private SwipeRefreshLayout mSwipeRefreshLayout;
-    private Switch mBluetoothSwitch;
-    private ProgressBar mProgressBar;
-    private PagedListView mDeviceListView;
-    private ViewSwitcher mViewSwitcher;
-    private TextView mMessageView;
-    private BluetoothDeviceListAdapter mDeviceAdapter;
-    private LocalBluetoothAdapter mLocalAdapter;
-    private LocalBluetoothManager mLocalManager;
-
-    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+    private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+    private final IntentFilter mIntentFilter = new IntentFilter(
+            BluetoothAdapter.ACTION_STATE_CHANGED);
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) {
-                setProgressBarVisible(true);
-                mBluetoothSwitch.setChecked(true);
-                if (mViewSwitcher.getCurrentView() != mSwipeRefreshLayout) {
-                    mViewSwitcher.showPrevious();
-                }
-            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
-                setProgressBarVisible(false);
-            }
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+            handleStateChanged(state);
         }
     };
+    private final CompoundButton.OnCheckedChangeListener mBluetoothSwitchListener =
+            new CompoundButton.OnCheckedChangeListener() {
+                @Override
+                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                    mBluetoothSwitch.setEnabled(false);
+                    if (isChecked) {
+                        mBluetoothAdapter.enable();
+                    } else {
+                        mBluetoothAdapter.disable();
+                    }
+                }
+            };
 
-    public static BluetoothSettingsFragment getInstance() {
-        BluetoothSettingsFragment bluetoothSettingsFragment = new BluetoothSettingsFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.bluetooth_settings);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.bluetooth_list);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_toggle);
-        bluetoothSettingsFragment.setArguments(bundle);
-        return bluetoothSettingsFragment;
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private Switch mBluetoothSwitch;
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.bluetooth_settings_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_toggle;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        mLocalBluetoothManager = BluetoothUtils.getLocalBtManager(context);
+        if (mLocalBluetoothManager == null) {
+            goBack();
+        }
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
-        mBluetoothSwitch = getActivity().findViewById(R.id.toggle_switch);
-        mSwipeRefreshLayout = getActivity().findViewById(R.id.swiperefresh);
-        mSwipeRefreshLayout.setSize(SwipeRefreshLayout.LARGE);
-        mSwipeRefreshLayout.setOnRefreshListener(
-                new SwipeRefreshLayout.OnRefreshListener() {
-                    @Override
-                    public void onRefresh() {
-                        mSwipeRefreshLayout.setRefreshing(false);
-                        if (mLocalAdapter.isDiscovering()) {
-                            mLocalAdapter.cancelDiscovery();
-                        }
-                        mDeviceAdapter.reset();
-                    }
-                }
-        );
-
-        mBluetoothSwitch.setOnCheckedChangeListener((v, isChecked) -> {
-                if (mBluetoothSwitch.isChecked()) {
-                    // bt scan was turned on at state listener, when state is on.
-                    mLocalAdapter.setBluetoothEnabled(true);
-                } else {
-                    mLocalAdapter.stopScanning();
-                    mLocalAdapter.setBluetoothEnabled(false);
-                }
-            });
-
-        mProgressBar = getView().findViewById(R.id.bt_search_progress);
-        mDeviceListView = getView().findViewById(R.id.list);
-        mViewSwitcher = getView().findViewById(R.id.view_switcher);
-        mMessageView = getView().findViewById(R.id.bt_message);
-
-        mLocalManager =
-                LocalBluetoothManager.getInstance(getContext(), /* onInitCallback= */ null);
-        if (mLocalManager == null) {
-            LOG.e("Bluetooth is not supported on this device");
-            return;
-        }
-        mLocalAdapter = mLocalManager.getBluetoothAdapter();
+        mBluetoothSwitch = requireActivity().findViewById(R.id.toggle_switch);
+        mBluetoothSwitch.setOnCheckedChangeListener(mBluetoothSwitchListener);
     }
 
     @Override
     public void onStart() {
         super.onStart();
-        if (mLocalManager == null) {
-            return;
-        }
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
-        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
-        getActivity().registerReceiver(mBroadcastReceiver, filter);
-
-        mLocalManager.setForegroundActivity(getActivity());
-        mLocalManager.getEventManager().registerCallback(this);
-        mBluetoothSwitch.setChecked(mLocalAdapter.isEnabled());
-        if (mLocalAdapter.isEnabled()) {
-            setProgressBarVisible(true);
-            mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
-            mLocalAdapter.startScanning(true);
-            if (mViewSwitcher.getCurrentView() != mSwipeRefreshLayout) {
-                mViewSwitcher.showPrevious();
-            }
-        } else {
-            setProgressBarVisible(false);
-            if (mViewSwitcher.getCurrentView() != mMessageView) {
-                mViewSwitcher.showNext();
-            }
-        }
-        mDeviceAdapter = new BluetoothDeviceListAdapter(
-                getContext() , mLocalManager, getFragmentController());
-        mDeviceListView.setAdapter(mDeviceAdapter);
-        mDeviceAdapter.start();
+        requireContext().registerReceiver(mReceiver, mIntentFilter);
+        mLocalBluetoothManager.setForegroundActivity(requireActivity());
+        handleStateChanged(mBluetoothAdapter.getState());
     }
 
     @Override
     public void onStop() {
         super.onStop();
-        if (mLocalManager == null) {
-            return;
-        }
-        getActivity().unregisterReceiver(mBroadcastReceiver);
-        mDeviceAdapter.stop();
-        mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
-        mLocalManager.setForegroundActivity(null);
-        mLocalAdapter.stopScanning();
-        mLocalManager.getEventManager().unregisterCallback(this);
+        requireContext().unregisterReceiver(mReceiver);
+        mLocalBluetoothManager.setForegroundActivity(null);
     }
 
-    @Override
-    public void onBluetoothStateChanged(int bluetoothState) {
-        switch (bluetoothState) {
-            case BluetoothAdapter.STATE_OFF:
-                setProgressBarVisible(false);
-                mBluetoothSwitch.setChecked(false);
-                if (mViewSwitcher.getCurrentView() != mMessageView) {
-                    mViewSwitcher.showNext();
-                }
+    private boolean isUserRestricted() {
+        return mCarUserManagerHelper.isCurrentProcessUserHasRestriction(DISALLOW_BLUETOOTH);
+    }
+
+    private void handleStateChanged(int state) {
+        // Momentarily clear the listener so that we don't update the adapter while trying to
+        // reflect the adapter state.
+        mBluetoothSwitch.setOnCheckedChangeListener(null);
+        switch (state) {
+            case BluetoothAdapter.STATE_TURNING_ON:
+                mBluetoothSwitch.setEnabled(false);
+                mBluetoothSwitch.setChecked(true);
                 break;
             case BluetoothAdapter.STATE_ON:
-            case BluetoothAdapter.STATE_TURNING_ON:
-                setProgressBarVisible(true);
+                mBluetoothSwitch.setEnabled(!isUserRestricted());
                 mBluetoothSwitch.setChecked(true);
-                if (mViewSwitcher.getCurrentView() != mSwipeRefreshLayout) {
-                        mViewSwitcher.showPrevious();
-                }
                 break;
             case BluetoothAdapter.STATE_TURNING_OFF:
-                setProgressBarVisible(true);
+                mBluetoothSwitch.setEnabled(false);
+                mBluetoothSwitch.setChecked(false);
                 break;
+            case BluetoothAdapter.STATE_OFF:
+            default:
+                mBluetoothSwitch.setEnabled(!isUserRestricted());
+                mBluetoothSwitch.setChecked(false);
         }
-    }
-
-    @Override
-    public void onScanningStateChanged(boolean started) {
-        if (!started) {
-            setProgressBarVisible(false);
-        }
-    }
-
-    @Override
-    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-        // no-op
-    }
-
-    @Override
-    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-        // no-op
-    }
-
-    @Override
-    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        // no-op
-    }
-
-    @Override
-    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-        // no-op
-    }
-
-    @Override
-    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
-        // no-op
-    }
-
-    @Override
-    public void onAudioModeChanged() {
-        // no-op
-    }
-
-    private  void setProgressBarVisible(boolean visible) {
-        if (mProgressBar != null) {
-            mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
-        }
+        mBluetoothSwitch.setOnCheckedChangeListener(mBluetoothSwitchListener);
     }
 }
diff --git a/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceController.java b/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceController.java
new file mode 100644
index 0000000..b460baa
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+/**
+ * Displays a list of unbonded (unpaired) Bluetooth devices. This controller also sets the
+ * Bluetooth adapter to discovery mode and begins scanning for discoverable devices for as long as
+ * the preference group is shown. Clicking on a device will start the pairing process. Discovery
+ * and scanning are halted while a device is pairing. Users with the {@link
+ * DISALLOW_CONFIG_BLUETOOTH} restriction cannot pair devices.
+ */
+public class BluetoothUnbondedDevicesPreferenceController extends
+        BluetoothScanningDevicesGroupPreferenceController {
+
+    private static final Logger LOG = new Logger(
+            BluetoothUnbondedDevicesPreferenceController.class);
+
+    public BluetoothUnbondedDevicesPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected BluetoothDeviceFilter.Filter getDeviceFilter() {
+        return BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER;
+    }
+
+    @Override
+    protected void onDeviceClickedInternal(CachedBluetoothDevice cachedDevice) {
+        if (cachedDevice.startPairing()) {
+            LOG.d("startPairing");
+            // Indicate that this client (vehicle) would like access to contacts (PBAP) and messages
+            // (MAP) if there is a server which permits it (usually a phone).
+            cachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+            cachedDevice.setMessagePermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+        } else {
+            BluetoothUtils.showError(getContext(), cachedDevice.getName(),
+                    R.string.bluetooth_pairing_error_message);
+            refreshUi();
+        }
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        int availabilityStatus = super.getAvailabilityStatus();
+        if (availabilityStatus == AVAILABLE
+                && getCarUserManagerHelper().isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)) {
+            return DISABLED_FOR_USER;
+        }
+        return availabilityStatus;
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/BluetoothUtils.java b/src/com/android/car/settings/bluetooth/BluetoothUtils.java
index e5531fd..731ee0b 100644
--- a/src/com/android/car/settings/bluetooth/BluetoothUtils.java
+++ b/src/com/android/car/settings/bluetooth/BluetoothUtils.java
@@ -16,14 +16,13 @@
 
 package com.android.car.settings.bluetooth;
 
+import android.app.AlertDialog;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.text.TextUtils;
 import android.widget.Toast;
 
-import androidx.car.app.CarAlertDialog;
-
 import com.android.car.settings.R;
 import com.android.car.settings.common.Logger;
 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
@@ -69,9 +68,9 @@
         String message = context.getString(messageResId, name);
         Context activity = manager.getForegroundActivity();
         if (manager.isForegroundActivity()) {
-            new CarAlertDialog.Builder(activity)
+            new AlertDialog.Builder(activity)
                     .setTitle(R.string.bluetooth_error_title)
-                    .setBody(message)
+                    .setMessage(message)
                     .setPositiveButton(android.R.string.ok, null)
                     .create()
                     .show();
@@ -127,7 +126,7 @@
                 return true;
             }
             if ((adapter.getDiscoveryEndMillis() +
-                GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) {
+                    GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) {
                 return true;
             }
         }
@@ -162,6 +161,13 @@
         return false;
     }
 
+    static void persistSelectedDeviceInPicker(Context context, String deviceAddress) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.putString(KEY_LAST_SELECTED_DEVICE, deviceAddress);
+        editor.putLong(KEY_LAST_SELECTED_DEVICE_TIME, System.currentTimeMillis());
+        editor.apply();
+    }
+
     public static LocalBluetoothManager getLocalBtManager(Context context) {
         return LocalBluetoothManager.getInstance(context, mOnInitCallback);
     }
diff --git a/src/com/android/car/settings/bluetooth/LocalRenameDialogFragment.java b/src/com/android/car/settings/bluetooth/LocalRenameDialogFragment.java
new file mode 100644
index 0000000..b4288f7
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/LocalRenameDialogFragment.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.annotation.StringRes;
+
+import com.android.car.settings.R;
+
+/** Dialog for changing the advertised name of the local bluetooth adapter. */
+public class LocalRenameDialogFragment extends BluetoothRenameDialogFragment {
+
+    /** Tag identifying the dialog for changing the name of the local Bluetooth adapter. */
+    public static final String TAG = "LocalBluetoothRename";
+
+    private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+    private final IntentFilter mFilter = new IntentFilter(
+            BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateDeviceName();
+        }
+    };
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        requireContext().registerReceiver(mReceiver, mFilter);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        requireContext().unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    @StringRes
+    protected int getDialogTitle() {
+        return R.string.bluetooth_rename_vehicle;
+    }
+
+    @Override
+    protected String getDeviceName() {
+        if (mBluetoothAdapter.isEnabled()) {
+            return mBluetoothAdapter.getName();
+        }
+        return null;
+    }
+
+    @Override
+    protected void setDeviceName(String deviceName) {
+        mBluetoothAdapter.setName(deviceName);
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/PairNewDevicePreferenceController.java b/src/com/android/car/settings/bluetooth/PairNewDevicePreferenceController.java
new file mode 100644
index 0000000..4b62afb
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/PairNewDevicePreferenceController.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.os.UserManager.DISALLOW_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+
+import androidx.lifecycle.LifecycleObserver;
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controls a preference that, when clicked, launches the page for pairing new Bluetooth devices.
+ * The associated preference for this controller should define the fragment attribute or an intent
+ * to launch for the Bluetooth device pairing page. If the adapter is not enabled, a click will
+ * enable Bluetooth. The summary message is updated to indicate this effect to the user.
+ */
+public class PairNewDevicePreferenceController extends PreferenceController<Preference> implements
+        LifecycleObserver {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final IntentFilter mIntentFilter = new IntentFilter(
+            BluetoothAdapter.ACTION_STATE_CHANGED);
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshUi();
+        }
+    };
+
+    public PairNewDevicePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (getPreference().getIntent() == null && getPreference().getFragment() == null) {
+            throw new IllegalStateException(
+                    "Preference should declare fragment or intent for page to pair new devices");
+        }
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        return isUserRestricted() ? DISABLED_FOR_USER : AVAILABLE;
+    }
+
+    private boolean isUserRestricted() {
+        return mCarUserManagerHelper.isCurrentProcessUserHasRestriction(DISALLOW_BLUETOOTH)
+                || mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH);
+    }
+
+    @Override
+    protected void onStartInternal() {
+        getContext().registerReceiver(mReceiver, mIntentFilter);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        getContext().unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(
+                BluetoothAdapter.getDefaultAdapter().isEnabled() ? "" : getContext().getString(
+                        R.string.bluetooth_pair_new_device_summary));
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        // Enable the adapter if it is not on (user is notified via summary message).
+        BluetoothAdapter.getDefaultAdapter().enable();
+        return false; // Don't handle so that preference framework will launch pairing fragment.
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/RemoteRenameDialogFragment.java b/src/com/android/car/settings/bluetooth/RemoteRenameDialogFragment.java
new file mode 100644
index 0000000..c220a25
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/RemoteRenameDialogFragment.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.StringRes;
+
+import com.android.car.settings.R;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/** Dialog for changing the display name of a remote bluetooth device. */
+public class RemoteRenameDialogFragment extends BluetoothRenameDialogFragment {
+
+    /** Tag identifying the dialog for changing the name of a remote Bluetooth device. */
+    public static final String TAG = "RemoteDeviceBluetoothRename";
+
+    private static final String KEY_CACHED_DEVICE_ADDRESS = "cached_device";
+
+    private CachedBluetoothDevice mCachedDevice;
+
+    /** Returns a new {@link RemoteRenameDialogFragment} instance for the given {@code device}. */
+    public static RemoteRenameDialogFragment newInstance(CachedBluetoothDevice device) {
+        Bundle args = new Bundle(1);
+        args.putString(KEY_CACHED_DEVICE_ADDRESS, device.getAddress());
+        RemoteRenameDialogFragment fragment = new RemoteRenameDialogFragment();
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        String deviceAddress = getArguments().getString(KEY_CACHED_DEVICE_ADDRESS);
+        LocalBluetoothManager manager = BluetoothUtils.getLocalBtManager(context);
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+                deviceAddress);
+        mCachedDevice = manager.getCachedDeviceManager().findDevice(device);
+    }
+
+    @Override
+    @StringRes
+    protected int getDialogTitle() {
+        return R.string.bluetooth_rename_device;
+    }
+
+    @Override
+    protected String getDeviceName() {
+        if (mCachedDevice != null) {
+            return mCachedDevice.getName();
+        }
+        return null;
+    }
+
+    @Override
+    protected void setDeviceName(String deviceName) {
+        if (mCachedDevice != null) {
+            mCachedDevice.setName(deviceName);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/bluetooth/Utf8ByteLengthFilter.java b/src/com/android/car/settings/bluetooth/Utf8ByteLengthFilter.java
new file mode 100644
index 0000000..2b7c3cc
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/Utf8ByteLengthFilter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import android.text.InputFilter;
+import android.text.Spanned;
+
+/**
+ * Filter which constrains edits so that the text length is not greater than the specified number
+ * of bytes using UTF-8 encoding.
+ *
+ * <p>The JNI method used by {@link android.server.BluetoothService} to convert UTF-16 to UTF-8
+ * doesn't support surrogate pairs, therefore code points outside of the basic multilingual plane
+ * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters rather than a single 4-byte
+ * UTF-8 encoding. Dalvik implements this conversion in {@code convertUtf16ToUtf8()} in
+ * {@code dalvik/vm/UtfString.c}.
+ *
+ * <p>This JNI method is unlikely to change in the future due to backwards compatibility
+ * requirements. It's also unclear whether the installed base of Bluetooth devices would
+ * correctly handle the encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6.
+ * However, this filter will still work in scenarios where surrogate pairs are encoded as 4
+ * bytes, with the caveat that the maximum length will be constrained more conservatively than
+ * necessary.
+ *
+ * <p>Note: borrowed from com.android.settings.bluetooth.Utf8ByteLengthFilter.
+ */
+class Utf8ByteLengthFilter implements InputFilter {
+    private final int mMaxBytes;
+
+    Utf8ByteLengthFilter(int maxBytes) {
+        mMaxBytes = maxBytes;
+    }
+
+    @Override
+    public CharSequence filter(CharSequence source, int start, int end,
+            Spanned dest, int dstart, int dend) {
+        int srcByteCount = 0;
+        // Count UTF-8 bytes in source substring.
+        for (int i = start; i < end; i++) {
+            char c = source.charAt(i);
+            srcByteCount += (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3);
+        }
+        int destLen = dest.length();
+        int destByteCount = 0;
+        // Count UTF-8 bytes in destination excluding replaced section.
+        for (int i = 0; i < destLen; i++) {
+            if (i < dstart || i >= dend) {
+                char c = dest.charAt(i);
+                destByteCount += (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3);
+            }
+        }
+        int keepBytes = mMaxBytes - destByteCount;
+        if (keepBytes <= 0) {
+            return "";
+        } else if (keepBytes >= srcByteCount) {
+            return null; // Use original source string.
+        } else {
+            // Find end position of largest sequence that fits in keepBytes.
+            for (int i = start; i < end; i++) {
+                char c = source.charAt(i);
+                keepBytes -= (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3);
+                if (keepBytes < 0) {
+                    return source.subSequence(start, i);
+                }
+            }
+            // If the entire substring fits, we should have returned null above, so this line should
+            // not be reached. If for some reason it is, use the original source string.
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/common/ActivityResultCallback.java b/src/com/android/car/settings/common/ActivityResultCallback.java
new file mode 100644
index 0000000..e94c9b5
--- /dev/null
+++ b/src/com/android/car/settings/common/ActivityResultCallback.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Handles activity results after a {@link FragmentController} fires {@link
+ * FragmentController#startActivityForResult(Intent, int, ActivityResultCallback)}
+ * or {@link FragmentController#startIntentSenderForResult}.
+ */
+public interface ActivityResultCallback {
+
+    /**
+     * Callback used when an activity started by
+     * {@link FragmentController#startActivityForResult(Intent,
+     * int, ActivityResultCallback)} or {@link FragmentController#startIntentSenderForResult}
+     * receives a result.
+     */
+    void processActivityResult(int requestCode, int resultCode, @Nullable Intent data);
+}
diff --git a/src/com/android/car/settings/common/AnimationUtil.java b/src/com/android/car/settings/common/AnimationUtil.java
deleted file mode 100644
index 4966c67..0000000
--- a/src/com/android/car/settings/common/AnimationUtil.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.common;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-
-import com.android.car.settings.R;
-
-/**
- * Contains util function for animations.
- */
-public class AnimationUtil {
-    private AnimationUtil() {
-    }
-
-    /**
-     * Animation that slide the new activity in from right.
-     */
-    public static ActivityOptions slideInFromRightOption(Context context) {
-        return ActivityOptions.makeCustomAnimation(
-                context, R.anim.trans_right_in , R.anim.trans_fade_out);
-    }
-}
diff --git a/src/com/android/car/settings/common/BaseCarSettingsActivity.java b/src/com/android/car/settings/common/BaseCarSettingsActivity.java
new file mode 100644
index 0000000..97243af
--- /dev/null
+++ b/src/com/android/car/settings/common/BaseCarSettingsActivity.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.car.apps.common.util.Themes;
+import com.android.car.settings.R;
+
+/**
+ * Base activity class for car settings, provides a action bar with a back button that goes to
+ * previous activity.
+ */
+public abstract class BaseCarSettingsActivity extends FragmentActivity implements
+        FragmentController, OnUxRestrictionsChangedListener, UxRestrictionsProvider,
+        OnBackStackChangedListener, PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+    private static final Logger LOG = new Logger(BaseCarSettingsActivity.class);
+
+    private CarUxRestrictionsHelper mUxRestrictionsHelper;
+    private View mRestrictedMessage;
+    // Default to minimum restriction.
+    private CarUxRestrictions mCarUxRestrictions = new CarUxRestrictions.Builder(
+            /* reqOpt= */ true,
+            CarUxRestrictions.UX_RESTRICTIONS_BASELINE,
+            /* timestamp= */ 0
+    ).build();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.car_setting_activity);
+        if (mUxRestrictionsHelper == null) {
+            mUxRestrictionsHelper = new CarUxRestrictionsHelper(/* context= */ this, /* listener= */
+                    this);
+        }
+        mUxRestrictionsHelper.start();
+        getSupportFragmentManager().addOnBackStackChangedListener(this);
+        mRestrictedMessage = findViewById(R.id.restricted_message);
+
+        launchIfDifferent(getInitialFragment());
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mUxRestrictionsHelper.stop();
+        mUxRestrictionsHelper = null;
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        hideKeyboard();
+        // If the backstack is empty, finish the activity.
+        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+            finish();
+        }
+    }
+
+    @Override
+    public void launchFragment(Fragment fragment) {
+        if (fragment instanceof DialogFragment) {
+            throw new IllegalArgumentException(
+                    "cannot launch dialogs with launchFragment() - use showDialog() instead");
+        }
+
+        getSupportFragmentManager()
+                .beginTransaction()
+                .setCustomAnimations(
+                        Themes.getAttrResourceId(/* context= */ this,
+                                android.R.attr.fragmentOpenEnterAnimation),
+                        Themes.getAttrResourceId(/* context= */ this,
+                                android.R.attr.fragmentOpenExitAnimation),
+                        Themes.getAttrResourceId(/* context= */ this,
+                                android.R.attr.fragmentCloseEnterAnimation),
+                        Themes.getAttrResourceId(/* context= */ this,
+                                android.R.attr.fragmentCloseExitAnimation))
+                .replace(R.id.fragment_container, fragment,
+                        Integer.toString(getSupportFragmentManager().getBackStackEntryCount()))
+                .addToBackStack(null)
+                .commit();
+    }
+
+    @Override
+    public void goBack() {
+        onBackPressed();
+    }
+
+    @Override
+    public void showBlockingMessage() {
+        Toast.makeText(this, R.string.restricted_while_driving, Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    public void showDialog(DialogFragment dialogFragment, @Nullable String tag) {
+        dialogFragment.show(getSupportFragmentManager(), tag);
+    }
+
+    @Override
+    @Nullable
+    public DialogFragment findDialogByTag(String tag) {
+        Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
+        if (fragment instanceof DialogFragment) {
+            return (DialogFragment) fragment;
+        }
+        return null;
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode,
+            ActivityResultCallback callback) {
+        throw new UnsupportedOperationException(
+                "Unimplemented for activities that implement FragmentController");
+    }
+
+    @Override
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, Bundle options,
+            ActivityResultCallback callback) {
+        throw new UnsupportedOperationException(
+                "Unimplemented for activities that implement FragmentController");
+    }
+
+    @Override
+    public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
+        mCarUxRestrictions = restrictionInfo;
+        Fragment currentFragment = getCurrentFragment();
+        if (currentFragment instanceof OnUxRestrictionsChangedListener) {
+            ((OnUxRestrictionsChangedListener) currentFragment)
+                    .onUxRestrictionsChanged(restrictionInfo);
+        }
+        updateBlockingView(currentFragment);
+    }
+
+    @Override
+    public CarUxRestrictions getCarUxRestrictions() {
+        return mCarUxRestrictions;
+    }
+
+    @Override
+    public void onBackStackChanged() {
+        onUxRestrictionsChanged(getCarUxRestrictions());
+    }
+
+    @Override
+    public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
+        if (pref.getFragment() != null) {
+            Fragment fragment = Fragment.instantiate(/* context= */ this, pref.getFragment(),
+                    pref.getExtras());
+            launchFragment(fragment);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Gets the fragment to show onCreate. If null, the activity will not perform an initial
+     * fragment transaction.
+     */
+    @Nullable
+    protected abstract Fragment getInitialFragment();
+
+    protected void launchIfDifferent(Fragment newFragment) {
+        Fragment currentFragment = getCurrentFragment();
+        if ((newFragment != null) && differentFragment(newFragment, currentFragment)) {
+            LOG.d("launchIfDifferent: " + newFragment + " replacing " + currentFragment);
+            launchFragment(newFragment);
+        }
+    }
+
+    protected Fragment getCurrentFragment() {
+        return getSupportFragmentManager().findFragmentById(R.id.fragment_container);
+    }
+
+    /**
+     * Returns {code true} if newFragment is different from current fragment.
+     */
+    private boolean differentFragment(Fragment newFragment, Fragment currentFragment) {
+        return (currentFragment == null)
+                || (!currentFragment.getClass().equals(newFragment.getClass()));
+    }
+
+    private void hideKeyboard() {
+        InputMethodManager imm = (InputMethodManager) this.getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
+    }
+
+    private void updateBlockingView(@Nullable Fragment currentFragment) {
+        if (currentFragment instanceof BaseFragment) {
+            boolean canBeShown = ((BaseFragment) currentFragment).canBeShown(mCarUxRestrictions);
+            mRestrictedMessage.setVisibility(canBeShown ? View.GONE : View.VISIBLE);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/common/BaseFragment.java b/src/com/android/car/settings/common/BaseFragment.java
index bcb615d..8b00e6b 100644
--- a/src/com/android/car/settings/common/BaseFragment.java
+++ b/src/com/android/car/settings/common/BaseFragment.java
@@ -11,83 +11,33 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.car.settings.common;
 
-import android.annotation.NonNull;
 import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
 import android.content.Context;
 import android.os.Bundle;
-import android.support.annotation.LayoutRes;
-import android.support.annotation.StringRes;
-import android.support.v4.app.Fragment;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import com.android.car.settings.R;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import androidx.fragment.app.Fragment;
 
-import java.util.Set;
+import com.android.car.settings.R;
 
 /**
  * Base fragment for setting activity.
  */
-public abstract class BaseFragment extends Fragment {
-    public static final String EXTRA_TITLE_ID = "extra_title_id";
-    public static final String EXTRA_LAYOUT = "extra_layout";
-    public static final String EXTRA_ACTION_BAR_LAYOUT = "extra_action_bar_layout";
-    /**
-     * For indicating a fragment is running in Setup Wizard
-     */
-    public static final String EXTRA_RUNNING_IN_SETUP_WIZARD = "extra_running_in_setup_wizard";
-
-    /**
-     * Controls the transition of fragment.
-     */
-    public interface FragmentController {
-        /**
-         * Launches fragment in the main container of current activity.
-         */
-        void launchFragment(BaseFragment fragment);
-
-        /**
-         * Pops the top off the fragment stack.
-         * @return {@code false} if there's no stack to pop, {@code true} otherwise
-         */
-        void goBack();
-
-        /**
-         * Shows a message that current feature is not available when driving.
-         */
-        void showDOBlockingMessage();
-    }
-
-    /**
-     * Provides current CarUxRestrictions.
-     */
-    public interface UXRestrictionsProvider {
-
-        /**
-         * Fetches current CarUxRestrictions
-         */
-        @NonNull
-        CarUxRestrictions getCarUxRestrictions();
-    }
-
-    @LayoutRes
-    protected int mLayout;
-
-    @LayoutRes
-    private int mActionBarLayout;
-
-    @StringRes
-    private int mTitleId;
+public abstract class BaseFragment extends Fragment implements
+        CarUxRestrictionsManager.OnUxRestrictionsChangedListener {
 
     /**
      * Assume The activity holds this fragment also implements the FragmentController.
@@ -98,17 +48,11 @@
     }
 
     /**
-     * Assume The activity holds this fragment also implements the UXRestrictionsProvider.
+     * Assume The activity holds this fragment also implements the UxRestrictionsProvider.
      * This function should be called after onAttach()
      */
     protected final CarUxRestrictions getCurrentRestrictions() {
-        return ((UXRestrictionsProvider) getActivity()).getCarUxRestrictions();
-    }
-
-    protected static Bundle getBundle() {
-        Bundle bundle = new Bundle();
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar);
-        return bundle;
+        return ((UxRestrictionsProvider) getActivity()).getCarUxRestrictions();
     }
 
     /**
@@ -119,60 +63,87 @@
         return !CarUxRestrictionsHelper.isNoSetup(carUxRestrictions);
     }
 
+    @Override
+    public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
+    }
+
     /**
-     * Notifies the fragment with the latest CarUxRestrictions change.
+     * Returns the layout id to use with the {@link ActionBar}. Subclasses should override this
+     * method to customize the action bar layout. The default action bar contains a back button
+     * and the title.
      */
-    protected void onUxRestrictionChanged(@NonNull CarUxRestrictions carUxRestrictions) {
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar;
+    }
+
+    /**
+     * Returns the layout id of the current Fragment.
+     */
+    @LayoutRes
+    protected abstract int getLayoutId();
+
+    /**
+     * Returns the string id for the current Fragment title. Subclasses should override this
+     * method to set the title to display. Use {@link #setTitle(CharSequence)} to update the
+     * displayed title while resumed. The default title is the Settings Activity label.
+     */
+    @StringRes
+    protected int getTitleId() {
+        return R.string.settings_label;
+    }
+
+    /**
+     * Should be used to override fragment's title. This should only be called after
+     * {@link #onActivityCreated(Bundle)}.
+     *
+     * @param title CharSequence to set as the new title.
+     */
+    protected final void setTitle(CharSequence title) {
+        TextView titleView = requireActivity().findViewById(R.id.title);
+        if (titleView != null) {
+            titleView.setText(title);
+        }
     }
 
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
         if (!(getActivity() instanceof FragmentController)) {
-            throw new IllegalArgumentException("Must attach to an FragmentController");
+            throw new IllegalStateException("Must attach to a FragmentController");
         }
-        if (!(getActivity() instanceof UXRestrictionsProvider)) {
-            throw new IllegalArgumentException("Must attach to an UXRestrictionsProvider");
+        if (!(getActivity() instanceof UxRestrictionsProvider)) {
+            throw new IllegalStateException("Must attach to a UxRestrictionsProvider");
         }
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Set<String> extraKeys = getArguments().keySet();
-        if (extraKeys.contains(EXTRA_ACTION_BAR_LAYOUT)) {
-            mActionBarLayout = getArguments().getInt(EXTRA_ACTION_BAR_LAYOUT);
-        } else {
-            throw new IllegalArgumentException("must specify a actionBar layout");
-        }
-        if (extraKeys.contains(EXTRA_LAYOUT)) {
-            mLayout = getArguments().getInt(EXTRA_LAYOUT);
-        } else {
-            throw new IllegalArgumentException("must specify a layout");
-        }
-        if (extraKeys.contains(EXTRA_TITLE_ID)) {
-            mTitleId = getArguments().getInt(EXTRA_TITLE_ID);
-        } else {
-            throw new IllegalArgumentException("must specify a title");
+    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        @LayoutRes int layoutId = getLayoutId();
+        return inflater.inflate(layoutId, container, false);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        FrameLayout actionBarContainer = requireActivity().findViewById(R.id.action_bar);
+        if (actionBarContainer != null) {
+            actionBarContainer.removeAllViews();
+            getLayoutInflater().inflate(getActionBarLayoutId(), actionBarContainer);
+
+            TextView titleView = actionBarContainer.requireViewById(R.id.title);
+            titleView.setText(getTitleId());
+            actionBarContainer.requireViewById(R.id.action_bar_icon_container).setOnClickListener(
+                    v -> onBackPressed());
         }
     }
 
+
     @Override
     public void onStart() {
         super.onStart();
-        onUxRestrictionChanged(getCurrentRestrictions());
-    }
-
-    /**
-     * Should be used to override fragment's title.
-     * Should be called after {@code super.onActivityCreated}, so that it's called AFTER the default title
-     * setter.
-     *
-     * @param title CharSequence to set as the new title.
-     */
-    protected final void setTitle(CharSequence title) {
-        TextView titleView = getActivity().findViewById(R.id.title);
-        titleView.setText(title);
+        onUxRestrictionsChanged(getCurrentRestrictions());
     }
 
     /**
@@ -181,26 +152,4 @@
     protected void onBackPressed() {
         getFragmentController().goBack();
     }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(mLayout, container, false);
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
-        actionBar.setDisplayHomeAsUpEnabled(false);
-        actionBar.setCustomView(mActionBarLayout);
-        actionBar.setDisplayShowCustomEnabled(true);
-        // make the toolbar take the whole width.
-        Toolbar toolbar = (Toolbar) actionBar.getCustomView().getParent();
-        toolbar.setPadding(0, 0, 0, 0);
-        getActivity().findViewById(R.id.action_bar_icon_container).setOnClickListener(
-                v -> onBackPressed());
-        TextView titleView = getActivity().findViewById(R.id.title);
-        titleView.setText(mTitleId);
-    }
 }
diff --git a/src/com/android/car/settings/common/ButtonPreference.java b/src/com/android/car/settings/common/ButtonPreference.java
new file mode 100644
index 0000000..28e4ad1
--- /dev/null
+++ b/src/com/android/car/settings/common/ButtonPreference.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.preference.Preference;
+
+/**
+ * {@link Preference} with a secondary clickable button on the side.
+ * {@link #setLayoutResource(int)} or the {@code widgetLayout} resource may be used to specify
+ * the icon to display in the button.
+ *
+ * <p>Note: the button is enabled even when {@link #isEnabled()} is {@code false}.
+ */
+public class ButtonPreference extends TwoActionPreference {
+
+    /**
+     * Interface definition for a callback to be invoked when the button is clicked.
+     */
+    public interface OnButtonClickListener {
+        /**
+         * Called when a button has been clicked.
+         *
+         * @param preference the preference whose button was clicked.
+         */
+        void onButtonClick(ButtonPreference preference);
+    }
+
+    private OnButtonClickListener mOnButtonClickListener;
+
+    public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public ButtonPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ButtonPreference(Context context) {
+        super(context);
+    }
+
+    /**
+     * Sets an {@link OnButtonClickListener} to be invoked when the button is clicked.
+     */
+    public void setOnButtonClickListener(OnButtonClickListener listener) {
+        mOnButtonClickListener = listener;
+    }
+
+    /** Virtually clicks the button contained inside this preference. */
+    public void performButtonClick() {
+        if (isActionShown()) {
+            if (mOnButtonClickListener != null) {
+                mOnButtonClickListener.onButtonClick(this);
+            }
+        }
+    }
+
+    @Override
+    protected void onBindWidgetFrame(View widgetFrame) {
+        widgetFrame.setOnClickListener(v -> performButtonClick());
+    }
+}
diff --git a/src/com/android/car/settings/common/CarSettingActivity.java b/src/com/android/car/settings/common/CarSettingActivity.java
index 3f3e3f8..1a33ccc 100644
--- a/src/com/android/car/settings/common/CarSettingActivity.java
+++ b/src/com/android/car/settings/common/CarSettingActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,143 +11,90 @@
  * 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
+ * limitations under the License.
  */
+
 package com.android.car.settings.common;
 
-import android.annotation.Nullable;
-import android.car.drivingstate.CarUxRestrictions;
-import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
-import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment.UXRestrictionsProvider;
-import com.android.car.settings.quicksettings.QuickSettingFragment;
 
 /**
- * Base activity class for car settings, provides a action bar with a back button that goes to
- * previous activity.
+ * Root activity used for most of the Settings app. This activity provides additional functionality
+ * which handles intents.
  */
-public class CarSettingActivity extends AppCompatActivity implements
-        BaseFragment.FragmentController, UXRestrictionsProvider, OnBackStackChangedListener{
-    private CarUxRestrictionsHelper mUxRestrictionsHelper;
-    private View mRestrictedMessage;
-    // Default to minimum restriction.
-    private CarUxRestrictions mCarUxRestrictions = new CarUxRestrictions.Builder(
-            /* reqOpt= */ true,
-            CarUxRestrictions.UX_RESTRICTIONS_BASELINE,
-            /* timestamp= */ 0
-    ).build();
+public class CarSettingActivity extends BaseCarSettingsActivity {
+
+    private static final Logger LOG = new Logger(CarSettingActivity.class);
+
+    private static final String KEY_HAS_NEW_INTENT =
+            "com.android.car.settings.common.CarSettingActivity.KEY_HAS_NEW_INTENT";
+
+    private boolean mHasNewIntent = true;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.app_compat_activity);
-        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
-        setSupportActionBar(toolbar);
-        if (mUxRestrictionsHelper == null) {
-            mUxRestrictionsHelper =
-                    new CarUxRestrictionsHelper(this, carUxRestrictions -> {
-                        mCarUxRestrictions = carUxRestrictions;
-                        BaseFragment currentFragment = getCurrentFragment();
-                        if (currentFragment != null) {
-                            currentFragment.onUxRestrictionChanged(carUxRestrictions);
-                            updateBlockingView(currentFragment);
-                        }
-                    });
-        }
-        mUxRestrictionsHelper.start();
-        getSupportFragmentManager().addOnBackStackChangedListener(this);
-        mRestrictedMessage = findViewById(R.id.restricted_message);
-    }
-
-    @Override
-    public void onBackStackChanged() {
-        updateBlockingView(getCurrentFragment());
-    }
-
-    private void updateBlockingView(@Nullable BaseFragment currentFragment) {
-        if (currentFragment == null) {
-            return;
-        }
-        boolean canBeShown = currentFragment.canBeShown(mCarUxRestrictions);
-        mRestrictedMessage.setVisibility(canBeShown ? View.GONE : View.VISIBLE);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        if (getCurrentFragment() == null) {
-            launchFragment(QuickSettingFragment.newInstance());
+        if (savedInstanceState != null) {
+            mHasNewIntent = savedInstanceState.getBoolean(KEY_HAS_NEW_INTENT, mHasNewIntent);
         }
     }
 
     @Override
-    public CarUxRestrictions getCarUxRestrictions() {
-        return mCarUxRestrictions;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mUxRestrictionsHelper.stop();
-        mUxRestrictionsHelper = null;
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_HAS_NEW_INTENT, mHasNewIntent);
     }
 
     @Override
     public void onNewIntent(Intent intent) {
+        LOG.d("onNewIntent" + intent);
         setIntent(intent);
+        mHasNewIntent = true;
     }
 
     @Override
-    public void launchFragment(BaseFragment fragment) {
-        getSupportFragmentManager()
-                .beginTransaction()
-                .setCustomAnimations(
-                        R.animator.trans_right_in ,
-                        R.animator.trans_left_out,
-                        R.animator.trans_left_in,
-                        R.animator.trans_right_out)
-                .replace(R.id.fragment_container, fragment)
-                .addToBackStack(null)
-                .commit();
-    }
-
-    @Override
-    public void goBack() {
-        onBackPressed();
-    }
-
-    @Override
-    public void showDOBlockingMessage() {
-        Toast.makeText(
-                this, R.string.restricted_while_driving, Toast.LENGTH_SHORT).show();
-    }
-
-    @Override
-    public void onBackPressed() {
-        super.onBackPressed();
-        hideKeyboard();
-        // if the backstack is empty, finish the activity.
-        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
-            finish();
+    protected void onResume() {
+        super.onResume();
+        if (mHasNewIntent) {
+            Fragment fragment = FragmentResolver.getFragmentForIntent(/* context= */ this,
+                    getIntent());
+            launchIfDifferent(fragment);
+            mHasNewIntent = false;
         }
     }
 
-    private BaseFragment getCurrentFragment() {
-        return (BaseFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
+    @Override
+    public void launchFragment(Fragment fragment) {
+        // Called before super to clear the back stack if necessary before launching the fragment
+        // in question.
+        if (fragment.getClass().getName().equals(
+                getString(R.string.config_settings_hierarchy_root_fragment))
+                && getSupportFragmentManager().getBackStackEntryCount() > 1) {
+            getSupportFragmentManager().popBackStackImmediate(/* name= */ null,
+                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
+        }
+
+        super.launchFragment(fragment);
     }
 
-    private void hideKeyboard() {
-        InputMethodManager imm = (InputMethodManager)this.getSystemService(
-                Context.INPUT_METHOD_SERVICE);
-        imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
+    /**
+     * Gets the fragment to show onCreate. This will only be launched if it is different from the
+     * current fragment shown.
+     */
+    @Override
+    @Nullable
+    protected Fragment getInitialFragment() {
+        if (getCurrentFragment() != null) {
+            return getCurrentFragment();
+        }
+        return Fragment.instantiate(this,
+                getString(R.string.config_settings_hierarchy_root_fragment));
     }
 }
diff --git a/src/com/android/car/settings/common/CarUxRestrictionsHelper.java b/src/com/android/car/settings/common/CarUxRestrictionsHelper.java
index 72664e9..e683661 100644
--- a/src/com/android/car/settings/common/CarUxRestrictionsHelper.java
+++ b/src/com/android/car/settings/common/CarUxRestrictionsHelper.java
@@ -24,7 +24,8 @@
 import android.content.Context;
 import android.content.ServiceConnection;
 import android.os.IBinder;
-import android.support.annotation.Nullable;
+
+import androidx.annotation.Nullable;
 
 /**
  * Class that helps registering {@link CarUxRestrictionsManager.OnUxRestrictionsChangedListener} and
diff --git a/src/com/android/car/settings/common/ConfirmationDialogFragment.java b/src/com/android/car/settings/common/ConfirmationDialogFragment.java
new file mode 100644
index 0000000..9b418b5
--- /dev/null
+++ b/src/com/android/car/settings/common/ConfirmationDialogFragment.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.fragment.app.DialogFragment;
+
+/**
+ * Common dialog that can be used across the settings app to ask the user to confirm their desired
+ * action.
+ */
+public class ConfirmationDialogFragment extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    /** Builder to help construct {@link ConfirmationDialogFragment}. */
+    public static class Builder {
+
+        private final Context mContext;
+        private Bundle mArgs;
+        private String mTitle;
+        private String mMessage;
+        private String mPosLabel;
+        private String mNegLabel;
+        private ConfirmListener mConfirmListener;
+        private RejectListener mRejectListener;
+
+        public Builder(Context context) {
+            mContext = context;
+        }
+
+        /** Sets the title. */
+        public Builder setTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /** Sets the title. */
+        public Builder setTitle(@StringRes int title) {
+            mTitle = mContext.getString(title);
+            return this;
+        }
+
+        /** Sets the message. */
+        public Builder setMessage(String message) {
+            mMessage = message;
+            return this;
+        }
+
+        /** Sets the message. */
+        public Builder setMessage(@StringRes int message) {
+            mMessage = mContext.getString(message);
+            return this;
+        }
+
+        /** Sets the positive button label. */
+        public Builder setPositiveButton(@StringRes int label, ConfirmListener confirmListener) {
+            mPosLabel = mContext.getString(label);
+            mConfirmListener = confirmListener;
+            return this;
+        }
+
+        /** Sets the negative button label. */
+        public Builder setNegativeButton(@StringRes int label, RejectListener rejectListener) {
+            mNegLabel = mContext.getString(label);
+            mRejectListener = rejectListener;
+            return this;
+        }
+
+        /** Adds an argument string to the argument bundle. */
+        public Builder addArgumentString(String argumentKey, String argument) {
+            if (mArgs == null) {
+                mArgs = new Bundle();
+            }
+            mArgs.putString(argumentKey, argument);
+            return this;
+        }
+
+        /** Adds an argument boolean to the argument bundle. */
+        public Builder addArgumentBoolean(String argumentKey, boolean argument) {
+            if (mArgs == null) {
+                mArgs = new Bundle();
+            }
+            mArgs.putBoolean(argumentKey, argument);
+            return this;
+        }
+
+        /** Adds an argument Parcelable to the argument bundle. */
+        public Builder addArgumentParcelable(String argumentKey, Parcelable argument) {
+            if (mArgs == null) {
+                mArgs = new Bundle();
+            }
+            mArgs.putParcelable(argumentKey, argument);
+            return this;
+        }
+
+        /** Constructs the {@link ConfirmationDialogFragment}. */
+        public ConfirmationDialogFragment build() {
+            return ConfirmationDialogFragment.init(this);
+        }
+    }
+
+    /** Identifier used to launch the dialog fragment. */
+    public static final String TAG = "ConfirmationDialogFragment";
+
+    // Argument keys are prefixed with TAG in order to reduce the changes of collision with user
+    // provided arguments.
+    private static final String ALL_ARGUMENTS_KEY = TAG + "_all_arguments";
+    private static final String ARGUMENTS_KEY = TAG + "_arguments";
+    private static final String TITLE_KEY = TAG + "_title";
+    private static final String MESSAGE_KEY = TAG + "_message";
+    private static final String POSITIVE_KEY = TAG + "_positive";
+    private static final String NEGATIVE_KEY = TAG + "_negative";
+
+    private String mTitle;
+    private String mMessage;
+    private String mPosLabel;
+    private String mNegLabel;
+    private ConfirmListener mConfirmListener;
+    private RejectListener mRejectListener;
+
+    /** Constructs the dialog fragment from the arguments provided in the {@link Builder} */
+    private static ConfirmationDialogFragment init(Builder builder) {
+        ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment();
+        Bundle args = new Bundle();
+        args.putBundle(ARGUMENTS_KEY, builder.mArgs);
+        args.putString(TITLE_KEY, builder.mTitle);
+        args.putString(MESSAGE_KEY, builder.mMessage);
+        args.putString(POSITIVE_KEY, builder.mPosLabel);
+        args.putString(NEGATIVE_KEY, builder.mNegLabel);
+        dialogFragment.setArguments(args);
+        dialogFragment.setConfirmListener(builder.mConfirmListener);
+        dialogFragment.setRejectListener(builder.mRejectListener);
+        return dialogFragment;
+    }
+
+    /**
+     * Since it is possible for the listeners to be unregistered on configuration change, provide a
+     * way to reattach the listeners.
+     */
+    public static void resetListeners(@Nullable ConfirmationDialogFragment dialogFragment,
+            @Nullable ConfirmListener confirmListener, @Nullable RejectListener rejectListener) {
+        if (dialogFragment != null) {
+            dialogFragment.setConfirmListener(confirmListener);
+            dialogFragment.setRejectListener(rejectListener);
+        }
+    }
+
+    /** Sets the listener which listens to a click on the positive button. */
+    private void setConfirmListener(ConfirmListener confirmListener) {
+        mConfirmListener = confirmListener;
+    }
+
+    /** Gets the listener which listens to a click on the positive button */
+    @Nullable
+    public ConfirmListener getConfirmListener() {
+        return mConfirmListener;
+    }
+
+    /** Sets the listener which listens to a click on the negative button. */
+    private void setRejectListener(RejectListener rejectListener) {
+        mRejectListener = rejectListener;
+    }
+
+    /** Gets the listener which listens to a click on the negative button. */
+    @Nullable
+    public RejectListener getRejectListener() {
+        return mRejectListener;
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Bundle args = getArguments();
+        if (savedInstanceState != null) {
+            args = savedInstanceState.getBundle(ALL_ARGUMENTS_KEY);
+        }
+
+        if (args != null) {
+            mTitle = getArguments().getString(TITLE_KEY);
+            mMessage = getArguments().getString(MESSAGE_KEY);
+            mPosLabel = getArguments().getString(POSITIVE_KEY);
+            mNegLabel = getArguments().getString(NEGATIVE_KEY);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBundle(ALL_ARGUMENTS_KEY, getArguments());
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+        if (!TextUtils.isEmpty(mTitle)) {
+            builder.setTitle(mTitle);
+        }
+        if (!TextUtils.isEmpty(mMessage)) {
+            builder.setMessage(mMessage);
+        }
+        if (!TextUtils.isEmpty(mPosLabel)) {
+            builder.setPositiveButton(mPosLabel, this);
+        }
+        if (!TextUtils.isEmpty(mNegLabel)) {
+            builder.setNegativeButton(mNegLabel, this);
+        }
+        return builder.create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            if (mConfirmListener != null) {
+                mConfirmListener.onConfirm(getArguments().getBundle(ARGUMENTS_KEY));
+            }
+        } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+            if (mRejectListener != null) {
+                mRejectListener.onReject(getArguments().getBundle(ARGUMENTS_KEY));
+            }
+        }
+    }
+
+    /** Listens to the confirmation action. */
+    public interface ConfirmListener {
+        /**
+         * Defines the action to take on confirm. The bundle will contain the arguments added when
+         * constructing the dialog through with {@link Builder#addArgumentString(String, String)}.
+         */
+        void onConfirm(@Nullable Bundle arguments);
+    }
+
+    /** Listens to the rejection action. */
+    public interface RejectListener {
+        /**
+         * Defines the action to take on reject. The bundle will contain the arguments added when
+         * constructing the dialog through with {@link Builder#addArgumentString(String, String)}.
+         */
+        void onReject(@Nullable Bundle arguments);
+    }
+}
diff --git a/src/com/android/car/settings/common/DOBlockingDialogFragment.java b/src/com/android/car/settings/common/DOBlockingDialogFragment.java
deleted file mode 100644
index c33c432..0000000
--- a/src/com/android/car/settings/common/DOBlockingDialogFragment.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.common;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-
-import androidx.car.app.CarAlertDialog;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment.FragmentController;
-import com.android.car.settings.quicksettings.QuickSettingFragment;
-
-/**
- * A dialog to block non-distraction optimized view when restriction is applied.
- */
-public class DOBlockingDialogFragment extends DialogFragment implements
-        DialogInterface.OnClickListener {
-    public static final String DIALOG_TAG = "block_dialog_tag";
-    private static final String MESSAGE_ARG_KEY = "message";
-    private boolean mShowQuickSettingsMainScreen = true;
-
-    /**
-     * Creates a DOBlockingDialogFragment with a specified message
-     *
-     * @param message
-     * @return an instance of DOBlockingDialogFragment
-     */
-    public static DOBlockingDialogFragment newInstance(String message) {
-        DOBlockingDialogFragment fragment = new DOBlockingDialogFragment();
-
-        Bundle args = new Bundle();
-        args.putString(MESSAGE_ARG_KEY, message);
-        fragment.setArguments(args);
-
-        return fragment;
-    }
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        Context context = getContext();
-        // If a message is not set, use the default message.
-        String message = getArguments().getString(MESSAGE_ARG_KEY);
-        if (message == null) {
-            message = getContext().getString(R.string.restricted_while_driving);
-        }
-        Dialog dialog = new CarAlertDialog.Builder(context)
-                .setBody(message)
-                .setPositiveButton(context.getString(R.string.okay),  /* listener= */ this)
-                .setCancelable(false)
-                .create();
-        return dialog;
-    }
-
-    /**
-     * Return to the quick settings main screen after the dialog is dismissed.
-     * @param showQuickSettingsMainScreen whether to return to the quick settings main screen, the
-     * default value is true
-     */
-    public void goBackToQuickSettingsMainScreen(boolean showQuickSettingsMainScreen) {
-        mShowQuickSettingsMainScreen = showQuickSettingsMainScreen;
-    }
-
-    // only one button, no need to check on negative.
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        if (mShowQuickSettingsMainScreen) {
-            ((FragmentController) getActivity()).launchFragment(
-                    QuickSettingFragment.newInstance());
-        }
-        dismiss();
-    }
-}
diff --git a/src/com/android/car/settings/common/DefaultRestrictionsPreferenceController.java b/src/com/android/car/settings/common/DefaultRestrictionsPreferenceController.java
new file mode 100644
index 0000000..bf479b0
--- /dev/null
+++ b/src/com/android/car/settings/common/DefaultRestrictionsPreferenceController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+/**
+ * Concrete implementation of {@link PreferenceController} which allows for applying the default
+ * {@link #getAvailabilityStatus()} and {@link #onApplyUxRestrictions(CarUxRestrictions)}
+ * behavior to preferences which do not require additional controller logic.
+ */
+public final class DefaultRestrictionsPreferenceController extends
+        PreferenceController<Preference> {
+
+    public DefaultRestrictionsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+}
diff --git a/src/com/android/car/settings/common/EditTextPreferenceDialogFragment.java b/src/com/android/car/settings/common/EditTextPreferenceDialogFragment.java
new file mode 100644
index 0000000..4026fd4
--- /dev/null
+++ b/src/com/android/car/settings/common/EditTextPreferenceDialogFragment.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.preference.EditTextPreference;
+
+/**
+ * Presents a dialog with an {@link EditText} associated with an {@link EditTextPreference}.
+ *
+ * <p>Note: CarSettings needs to use custom dialog implementations in order to launch the platform
+ * {@link AlertDialog} instead of the one in the support library.
+ */
+public class EditTextPreferenceDialogFragment extends
+        SettingsPreferenceDialogFragment implements TextView.OnEditorActionListener {
+
+    private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogFragment.text";
+
+    private EditText mEditText;
+    private CharSequence mText;
+    private boolean mAllowEnterToSubmit = true;
+
+    /**
+     * Returns a new instance of {@link EditTextPreferenceDialogFragment} for the {@link
+     * EditTextPreference} with the given {@code key}.
+     */
+    public static EditTextPreferenceDialogFragment newInstance(String key) {
+        EditTextPreferenceDialogFragment fragment = new EditTextPreferenceDialogFragment();
+        Bundle b = new Bundle(/* capacity= */ 1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            mText = getEditTextPreference().getText();
+        } else {
+            mText = savedInstanceState.getCharSequence(SAVE_STATE_TEXT);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putCharSequence(SAVE_STATE_TEXT, mText);
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        mEditText = view.findViewById(android.R.id.edit);
+
+        if (mEditText == null) {
+            throw new IllegalStateException(
+                    "Dialog view must contain an EditText with id @android:id/edit");
+        }
+
+        mEditText.requestFocus();
+        mEditText.setText(mText);
+        mEditText.setInputType(InputType.TYPE_CLASS_TEXT);
+        mEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
+        mEditText.setOnEditorActionListener(this);
+        // Place cursor at the end
+        mEditText.setSelection(mEditText.getText().length());
+    }
+
+    private EditTextPreference getEditTextPreference() {
+        return (EditTextPreference) getPreference();
+    }
+
+    @Override
+    protected boolean needInputMethod() {
+        return true;
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            String value = mEditText.getText().toString();
+            if (getEditTextPreference().callChangeListener(value)) {
+                getEditTextPreference().setText(value);
+            }
+        }
+    }
+
+    /** Allows enabling and disabling the ability to press enter to dismiss the dialog. */
+    public void setAllowEnterToSubmit(boolean isAllowed) {
+        mAllowEnterToSubmit = isAllowed;
+    }
+
+    /** Allows verifying if enter to submit is currently enabled. */
+    public boolean getAllowEnterToSubmit() {
+        return mAllowEnterToSubmit;
+    }
+
+    @Override
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (actionId == EditorInfo.IME_ACTION_DONE && mAllowEnterToSubmit) {
+            CharSequence newValue = v.getText();
+
+            getEditTextPreference().callChangeListener(newValue);
+            dismiss();
+
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/car/settings/common/ErrorDialog.java b/src/com/android/car/settings/common/ErrorDialog.java
new file mode 100644
index 0000000..8872577
--- /dev/null
+++ b/src/com/android/car/settings/common/ErrorDialog.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Bundle;
+
+import androidx.annotation.StringRes;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+
+/**
+ * Dialog to inform that an action failed.
+ */
+public class ErrorDialog extends DialogFragment {
+    private static final String ERROR_DIALOG_TITLE_KEY = "error_dialog_title";
+    private static final String DIALOG_TAG = "ErrorDialogTag";
+
+    /**
+     * Shows the error dialog.
+     *
+     * @param parent Fragment associated with the dialog.
+     * @param title  Title for the error dialog.
+     */
+    public static ErrorDialog show(Fragment parent, @StringRes int title) {
+        ErrorDialog dialog = newInstance(title);
+        dialog.setTargetFragment(parent, 0);
+        dialog.show(parent.getFragmentManager(), DIALOG_TAG);
+        return dialog;
+    }
+
+    /**
+     * Creates an instance of error dialog with the appropriate title. This constructor should be
+     * used when starting an error dialog when we don't have a reference to the parent fragment.
+     */
+    public static ErrorDialog newInstance(@StringRes int title) {
+        ErrorDialog dialog = new ErrorDialog();
+        Bundle bundle = new Bundle();
+        bundle.putInt(ERROR_DIALOG_TITLE_KEY, title);
+        dialog.setArguments(bundle);
+        return dialog;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getContext())
+                .setTitle(getArguments().getInt(ERROR_DIALOG_TITLE_KEY))
+                .setPositiveButton(android.R.string.ok, /* listener =*/ null)
+                .create();
+    }
+}
diff --git a/src/com/android/car/settings/common/ExtraSettingsLoader.java b/src/com/android/car/settings/common/ExtraSettingsLoader.java
index 29fe307..6d81060 100644
--- a/src/com/android/car/settings/common/ExtraSettingsLoader.java
+++ b/src/com/android/car/settings/common/ExtraSettingsLoader.java
@@ -16,7 +16,7 @@
 
 package com.android.car.settings.common;
 
-import static com.android.settingslib.drawer.TileUtils.EXTRA_SETTINGS_ACTION;
+import static com.android.settingslib.drawer.CategoryKey.CATEGORY_DEVICE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
@@ -32,49 +32,44 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 
-import androidx.car.widget.ListItem;
-import androidx.car.widget.TextListItem;
+import androidx.preference.Preference;
 
+import com.android.car.apps.common.util.Themes;
 import com.android.car.settings.R;
 
-import java.util.Collection;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
 /**
  * Loads Activity with TileUtils.EXTRA_SETTINGS_ACTION.
  */
+// TODO: investigate using SettingsLib Tiles.
 public class ExtraSettingsLoader {
     private static final Logger LOG = new Logger(ExtraSettingsLoader.class);
     private static final String META_DATA_PREFERENCE_CATEGORY = "com.android.settings.category";
-    public static final String WIRELESS_CATEGORY = "com.android.settings.category.wireless";
-    public static final String DEVICE_CATEGORY = "com.android.settings.category.device";
-    public static final String SYSTEM_CATEGORY = "com.android.settings.category.system";
-    public static final String PERSONAL_CATEGORY = "com.android.settings.category.personal";
+    private Map<Preference, Bundle> mPreferenceBundleMap;
     private final Context mContext;
 
     public ExtraSettingsLoader(Context context) {
         mContext = context;
+        mPreferenceBundleMap = new HashMap<>();
     }
 
     /**
-     * Returns a map of category and setting items pair loaded from 3rd party.
+     * Returns a map of {@link Preference} and {@link Bundle} representing settings injected from
+     * system apps and their metadata. The given intent must specify the action to use for
+     * resolving activities and a category with the key "com.android.settings.category" and one of
+     * the values in {@link com.android.settingslib.drawer.CategoryKey}.
+     *
+     * @param intent intent specifying the extra settings category to load
      */
-    public Map<String, Collection<ListItem>> load() {
+    public Map<Preference, Bundle> loadPreferences(Intent intent) {
         PackageManager pm = mContext.getPackageManager();
-        Intent intent = new Intent(EXTRA_SETTINGS_ACTION);
-        Map<String, Collection<ListItem>> extraSettings = new HashMap<>();
-        // initialize the categories
-        extraSettings.put(WIRELESS_CATEGORY, new LinkedList());
-        extraSettings.put(DEVICE_CATEGORY, new LinkedList());
-        extraSettings.put(SYSTEM_CATEGORY, new LinkedList());
-        extraSettings.put(PERSONAL_CATEGORY, new LinkedList());
-
         List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
                 PackageManager.GET_META_DATA, ActivityManager.getCurrentUser());
 
+        String extraCategory = intent.getStringExtra(META_DATA_PREFERENCE_CATEGORY);
         for (ResolveInfo resolved : results) {
             if (!resolved.system) {
                 // Do not allow any app to be added to settings, only system ones.
@@ -129,18 +124,24 @@
             }
             Intent extraSettingIntent =
                     new Intent().setClassName(activityInfo.packageName, activityInfo.name);
-            if (category == null || !extraSettings.containsKey(category)) {
+            if (category == null) {
                 // If category is not specified or not supported, default to device.
-                category = DEVICE_CATEGORY;
+                category = CATEGORY_DEVICE;
             }
-            TextListItem item = new TextListItem(mContext);
-            item.setTitle(title);
-            item.setBody(summary);
-            item.setPrimaryActionIcon(icon.loadDrawable(mContext), /* useLargeIcon= */ false);
-            item.setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-            item.setOnClickListener(v -> mContext.startActivity(extraSettingIntent));
-            extraSettings.get(category).add(item);
+
+            if (!TextUtils.equals(extraCategory, category)) {
+                continue;
+            }
+            Preference preference = new Preference(mContext);
+            preference.setTitle(title);
+            preference.setSummary(summary);
+            if (icon != null) {
+                preference.setIcon(icon.loadDrawable(mContext));
+                preference.getIcon().setTint(Themes.getAttrColor(mContext, R.attr.iconColor));
+            }
+            preference.setIntent(extraSettingIntent);
+            mPreferenceBundleMap.put(preference, metaData);
         }
-        return extraSettings;
+        return mPreferenceBundleMap;
     }
 }
diff --git a/src/com/android/car/settings/common/ExtraSettingsPreferenceController.java b/src/com/android/car/settings/common/ExtraSettingsPreferenceController.java
new file mode 100644
index 0000000..11db5ab
--- /dev/null
+++ b/src/com/android/car/settings/common/ExtraSettingsPreferenceController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import java.util.Map;
+
+/**
+ * Injects preferences from other system applications at a placeholder location. The placeholder
+ * should be a {@link PreferenceGroup} which sets the controller attribute to the fully qualified
+ * name of this class. The preference should contain an intent which will be passed to
+ * {@link ExtraSettingsLoader#loadPreferences(Intent)}.
+ *
+ * <p>For example:
+ * <pre>{@code
+ * <PreferenceCategory
+ *     android:key="@string/pk_system_extra_settings"
+ *     android:title="@string/system_extra_settings_title"
+ *     settings:controller="com.android.settings.common.ExtraSettingsPreferenceController">
+ *     <intent android:action="com.android.settings.action.EXTRA_SETTINGS">
+ *         <extra android:name="com.android.settings.category"
+ *                android:value="com.android.settings.category.system"/>
+ *     </intent>
+ * </PreferenceCategory>
+ * }</pre>
+ *
+ * @see ExtraSettingsLoader
+ */
+// TODO: investigate using SettingsLib Tiles.
+public class ExtraSettingsPreferenceController extends PreferenceController<PreferenceGroup> {
+
+    private ExtraSettingsLoader mExtraSettingsLoader;
+    private boolean mSettingsLoaded;
+
+    public ExtraSettingsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions restrictionInfo) {
+        super(context, preferenceKey, fragmentController, restrictionInfo);
+        mExtraSettingsLoader = new ExtraSettingsLoader(context);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    public void setExtraSettingsLoader(ExtraSettingsLoader extraSettingsLoader) {
+        mExtraSettingsLoader = extraSettingsLoader;
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        Map<Preference, Bundle> preferenceBundleMap = mExtraSettingsLoader.loadPreferences(
+                preference.getIntent());
+        if (!mSettingsLoaded) {
+            addExtraSettings(preferenceBundleMap);
+            mSettingsLoaded = true;
+        }
+        preference.setVisible(preference.getPreferenceCount() > 0);
+    }
+
+    /**
+     * Adds the extra settings from the system based on the intent that is passed in the preference
+     * group. All the preferences that resolve these intents will be added in the preference group.
+     *
+     * @param preferenceBundleMap a map of {@link Preference} and {@link Bundle} representing
+     * settings injected from system apps and their metadata.
+     */
+    protected void addExtraSettings(Map<Preference, Bundle> preferenceBundleMap) {
+        for (Preference setting : preferenceBundleMap.keySet()) {
+            getPreference().addPreference(setting);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/common/FragmentController.java b/src/com/android/car/settings/common/FragmentController.java
new file mode 100644
index 0000000..404dac0
--- /dev/null
+++ b/src/com/android/car/settings/common/FragmentController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+
+/**
+ * Controls launching {@link Fragment} instances and back navigation.
+ */
+public interface FragmentController {
+
+    /**
+     * Launches a Fragment in the main container of the current Activity. This cannot be used to
+     * show dialog fragments and will throw an IllegalArgumentException if attempted. The method
+     * {@link #showDialog} should be used instead.
+     */
+    void launchFragment(Fragment fragment);
+
+    /**
+     * Pops the top off the Fragment stack.
+     */
+    void goBack();
+
+    /**
+     * Shows a message to inform the user that the current feature is not available when driving.
+     */
+    void showBlockingMessage();
+
+    /**
+     * Shows dialog with given tag.
+     */
+    void showDialog(DialogFragment dialogFragment, @Nullable String tag);
+
+    /**
+     * Finds dialog by tag. This is primarily used to reattach listeners to dialogs after
+     * configuration change. This method will return null if the tag references a fragment that
+     * isn't a dialog fragment or no dialog with the given tag exists.
+     */
+    @Nullable
+    DialogFragment findDialogByTag(String tag);
+
+    /**
+     * Starts an activity for a result. When the result is received, the {@link
+     * ActivityResultCallback} is passed the result. Note that the implementer of this interface
+     * must ensure that the callback is valid throughout the lifecycle of the new activity that is
+     * created.
+     *
+     * @param intent      The intent used to start an activity.
+     * @param requestCode User defined code which is passed to the callback when the activity exits.
+     *                    Values must use the first 8 bits of the int (i.e. 0-255).
+     * @param callback    Defines how the result from the started activity should be handled.
+     */
+    void startActivityForResult(Intent intent, int requestCode, ActivityResultCallback callback);
+
+    /**
+     * Starts an intent sender for a result. When the result is received, the {@link
+     * ActivityResultCallback} is passed the result. Note that the implementer of this interface
+     * must ensure that the callback is valid throughout the lifecycle of the new activity that is
+     * created.
+     *
+     * @param intent       The IntentSender to launch.
+     * @param requestCode  User defined code which is passed to the callback when the activity
+     *                     exits. Values must use the first 8 bits of the int (i.e. 0-255).
+     * @param fillInIntent If non-null, this will be provided as the intent parameter to {@link
+     *                     IntentSender#sendIntent}.
+     * @param flagsMask    Intent flags in the original IntentSender that you would like to change.
+     * @param flagsValues  Desired values for any bits set in <var>flagsMask</var>
+     * @param options      Additional options for how the Activity should be started.
+     * @param callback     Defines how the result from the started IntentSender should be handled.
+     */
+    void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, Bundle options,
+            ActivityResultCallback callback)
+            throws IntentSender.SendIntentException;
+}
diff --git a/src/com/android/car/settings/common/FragmentResolver.java b/src/com/android/car/settings/common/FragmentResolver.java
new file mode 100644
index 0000000..8c5b0d4
--- /dev/null
+++ b/src/com/android/car/settings/common/FragmentResolver.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.R;
+import com.android.car.settings.accounts.AccountSettingsFragment;
+import com.android.car.settings.accounts.ChooseAccountFragment;
+import com.android.car.settings.applications.ApplicationDetailsFragment;
+import com.android.car.settings.applications.ApplicationsSettingsFragment;
+import com.android.car.settings.applications.DefaultApplicationsSettingsFragment;
+import com.android.car.settings.applications.assist.ManageAssistFragment;
+import com.android.car.settings.applications.defaultapps.DefaultAutofillPickerFragment;
+import com.android.car.settings.applications.specialaccess.ModifySystemSettingsFragment;
+import com.android.car.settings.applications.specialaccess.NotificationAccessFragment;
+import com.android.car.settings.applications.specialaccess.UsageAccessFragment;
+import com.android.car.settings.bluetooth.BluetoothSettingsFragment;
+import com.android.car.settings.datausage.DataUsageFragment;
+import com.android.car.settings.datetime.DatetimeSettingsFragment;
+import com.android.car.settings.display.DisplaySettingsFragment;
+import com.android.car.settings.home.HomepageFragment;
+import com.android.car.settings.inputmethod.KeyboardFragment;
+import com.android.car.settings.language.LanguagePickerFragment;
+import com.android.car.settings.location.LocationScanningFragment;
+import com.android.car.settings.network.MobileNetworkFragment;
+import com.android.car.settings.quicksettings.QuickSettingFragment;
+import com.android.car.settings.sound.SoundSettingsFragment;
+import com.android.car.settings.storage.StorageSettingsFragment;
+import com.android.car.settings.system.AboutSettingsFragment;
+import com.android.car.settings.users.UsersListFragment;
+import com.android.car.settings.wifi.WifiSettingsFragment;
+import com.android.car.settings.wifi.preferences.WifiPreferencesFragment;
+
+
+/**
+ * Maps an Action string to a {@link Fragment} that can handle this Action.
+ */
+public class FragmentResolver {
+
+    private static final Logger LOG = new Logger(FragmentResolver.class);
+
+    private FragmentResolver() {
+    }
+
+    /**
+     * Returns a {@link Fragment} that can handle the given action, returns {@code null} if no
+     * {@link Fragment} that can handle this {@link Intent} can be found. Keep the order of intent
+     * actions same as in the manifest.
+     */
+    @Nullable
+    static Fragment getFragmentForIntent(Context context, @Nullable Intent intent) {
+        if (intent == null) {
+            return null;
+        }
+        String action = intent.getAction();
+        if (action == null) {
+            return null;
+        }
+        switch (action) {
+            case Settings.ACTION_SETTINGS:
+                return new HomepageFragment();
+
+            case Settings.ACTION_NIGHT_DISPLAY_SETTINGS:
+                return new QuickSettingFragment();
+
+            case Settings.ACTION_DISPLAY_SETTINGS:
+                return new DisplaySettingsFragment();
+
+            case Settings.ACTION_SOUND_SETTINGS:
+                return new SoundSettingsFragment();
+
+            case android.net.wifi.WifiManager.ACTION_PICK_WIFI_NETWORK:
+            case Settings.ACTION_WIFI_SETTINGS:
+            case Settings.ACTION_WIRELESS_SETTINGS:
+                return new WifiSettingsFragment();
+
+            case Settings.ACTION_WIFI_IP_SETTINGS:
+                return new WifiPreferencesFragment();
+
+            case Settings.ACTION_DATA_USAGE_SETTINGS:
+            case Settings.ACTION_MOBILE_DATA_USAGE:
+                return new DataUsageFragment();
+
+            case Settings.ACTION_DATA_ROAMING_SETTINGS:
+            case Settings.ACTION_NETWORK_OPERATOR_SETTINGS:
+                return new MobileNetworkFragment();
+
+            case Settings.ACTION_BLUETOOTH_SETTINGS:
+                return new BluetoothSettingsFragment();
+
+            case Settings.ACTION_LOCATION_SCANNING_SETTINGS:
+                return new LocationScanningFragment();
+
+            case Settings.ACTION_APPLICATION_SETTINGS:
+            case Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS:
+            case Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS:
+                return new ApplicationsSettingsFragment();
+
+            case Settings.ACTION_APPLICATION_DETAILS_SETTINGS:
+            case Settings.ACTION_NOTIFICATION_SETTINGS:
+            case Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS:
+            case Settings.ACTION_APP_NOTIFICATION_SETTINGS:
+                String pkg = intent.getStringExtra(Settings.EXTRA_APP_PACKAGE);
+                if (TextUtils.isEmpty(pkg)) {
+                    LOG.w("No package provided for application detailed intent");
+                    Uri uri = intent.getData();
+                    if (uri == null) {
+                        LOG.w("No uri provided for application detailed intent");
+                        return null;
+                    }
+                    pkg = uri.getSchemeSpecificPart();
+                }
+                return ApplicationDetailsFragment.getInstance(pkg);
+
+            case Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS:
+                return new DefaultApplicationsSettingsFragment();
+
+            case Settings.ACTION_VOICE_INPUT_SETTINGS:
+                return new ManageAssistFragment();
+
+            case Settings.ACTION_MANAGE_WRITE_SETTINGS:
+                return new ModifySystemSettingsFragment();
+
+            case Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS:
+                return new NotificationAccessFragment();
+
+            case Settings.ACTION_USAGE_ACCESS_SETTINGS:
+                return new UsageAccessFragment();
+
+            case Intent.ACTION_QUICK_CLOCK:
+            case Settings.ACTION_DATE_SETTINGS:
+                return new DatetimeSettingsFragment();
+
+            case Settings.ACTION_USER_SETTINGS:
+                return new UsersListFragment();
+
+            case Settings.ACTION_ADD_ACCOUNT:
+                return new ChooseAccountFragment();
+
+            case Settings.ACTION_SYNC_SETTINGS:
+                return new AccountSettingsFragment();
+
+            case Settings.ACTION_INTERNAL_STORAGE_SETTINGS:
+                return new StorageSettingsFragment();
+
+            case Settings.ACTION_LOCALE_SETTINGS:
+                return new LanguagePickerFragment();
+
+            case Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE:
+                return new DefaultAutofillPickerFragment();
+
+            case Settings.ACTION_INPUT_METHOD_SETTINGS:
+                return new KeyboardFragment();
+
+            case Settings.ACTION_DEVICE_INFO_SETTINGS:
+            case Settings.DEVICE_NAME_SETTINGS:
+                return new AboutSettingsFragment();
+
+            default:
+                return Fragment.instantiate(context,
+                        context.getString(R.string.config_settings_hierarchy_root_fragment));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/common/ListController.java b/src/com/android/car/settings/common/ListController.java
deleted file mode 100644
index e863f92..0000000
--- a/src/com/android/car/settings/common/ListController.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.common;
-
-/**
- * Interface of list adapter for ListItem.
- */
-public interface ListController {
-    /**
-     * Triggers UI update on the list.
-     */
-    void refreshList();
-}
diff --git a/src/com/android/car/settings/common/ListItemSettingsFragment.java b/src/com/android/car/settings/common/ListItemSettingsFragment.java
deleted file mode 100644
index 829b1c1..0000000
--- a/src/com/android/car/settings/common/ListItemSettingsFragment.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.settings.common;
-
-import android.os.Bundle;
-
-import androidx.car.widget.ListItemAdapter;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.PagedListView;
-
-import com.android.car.settings.R;
-
-/**
- * Settings page that only contain a list of items.
- * <p>
- * Uses support library ListItemAdapter, unlike ListSettingsFragment that uses the car-list
- * lists.
- */
-public abstract class ListItemSettingsFragment extends BaseFragment implements ListController {
-    private ListItemAdapter mListAdapter;
-
-    /**
-     * Gets bundle adding the list_fragment layout to it.
-     */
-    protected static Bundle getBundle() {
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_LAYOUT, R.layout.list_fragment);
-        return bundle;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        mListAdapter = new ListItemAdapter(getContext(), getItemProvider());
-
-        PagedListView listView = getView().findViewById(R.id.list);
-        listView.setAdapter(mListAdapter);
-        listView.setDividerVisibilityManager(mListAdapter);
-    }
-
-    @Override
-    public void refreshList() {
-        mListAdapter.notifyDataSetChanged();
-    }
-
-    /**
-     * Called in onActivityCreated.
-     * Gets ListItemProvider that should provide items to show up in the list.
-     */
-    public abstract ListItemProvider getItemProvider();
-}
diff --git a/src/com/android/car/settings/common/ListSettingsFragment.java b/src/com/android/car/settings/common/ListSettingsFragment.java
deleted file mode 100644
index 0b32735..0000000
--- a/src/com/android/car/settings/common/ListSettingsFragment.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.common;
-
-import android.os.Bundle;
-
-import androidx.car.widget.DayNightStyle;
-import androidx.car.widget.PagedListView;
-
-import com.android.car.list.TypedPagedListAdapter;
-import com.android.car.settings.R;
-
-import java.util.ArrayList;
-
-/**
- * Settings page that only contain a list of items.
- */
-public abstract class ListSettingsFragment extends BaseFragment {
-
-    protected PagedListView mListView;
-    protected TypedPagedListAdapter mPagedListAdapter;
-
-    protected static Bundle getBundle() {
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_LAYOUT, R.layout.list_fragment);
-        return bundle;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        mListView = (PagedListView) getView().findViewById(R.id.list);
-        mListView.setDayNightStyle(DayNightStyle.AUTO);
-        mPagedListAdapter = new TypedPagedListAdapter(getLineItems());
-        mListView.setAdapter(mPagedListAdapter);
-    }
-
-    /**
-     * Gets a List of LineItems to show up in this activity.
-     */
-    public abstract ArrayList<TypedPagedListAdapter.LineItem> getLineItems();
-}
diff --git a/src/com/android/car/settings/common/LogicalPreferenceGroup.java b/src/com/android/car/settings/common/LogicalPreferenceGroup.java
new file mode 100644
index 0000000..a3a2c45
--- /dev/null
+++ b/src/com/android/car/settings/common/LogicalPreferenceGroup.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.R;
+
+/**
+ * {@link PreferenceGroup} which does not display a title, icon, or summary. This allows for
+ * logical grouping of preferences without indications in the UI.
+ */
+public class LogicalPreferenceGroup extends PreferenceGroup {
+
+    public LogicalPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        setLayoutResource(R.layout.logical_preference_group);
+    }
+
+    public LogicalPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public LogicalPreferenceGroup(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LogicalPreferenceGroup(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        holder.setDividerAllowedAbove(false);
+    }
+}
diff --git a/src/com/android/car/settings/common/MasterSwitchPreference.java b/src/com/android/car/settings/common/MasterSwitchPreference.java
new file mode 100644
index 0000000..2156cce
--- /dev/null
+++ b/src/com/android/car/settings/common/MasterSwitchPreference.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+import com.android.car.settings.R;
+
+/** A preference that can be clicked on one side and toggled on another. */
+public class MasterSwitchPreference extends TwoActionPreference {
+
+    /**
+     * Interface definition for a callback to be invoked when the switch is toggled.
+     */
+    public interface OnSwitchToggleListener {
+        /**
+         * Called when a switch was toggled.
+         *
+         * @param preference the preference whose switch was toggled.
+         * @param isChecked  the new state of the switch.
+         */
+        void onToggle(MasterSwitchPreference preference, boolean isChecked);
+    }
+
+    private Switch mSwitch;
+    private boolean mIsChecked;
+    private OnSwitchToggleListener mToggleListener;
+
+    private final CompoundButton.OnCheckedChangeListener mCheckedChangeListener =
+            (buttonView, isChecked) -> {
+                if (mToggleListener != null) {
+                    mToggleListener.onToggle(this, isChecked);
+                }
+            };
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context) {
+        super(context);
+        init();
+    }
+
+    private void init() {
+        setWidgetLayoutResource(R.layout.master_switch_widget);
+    }
+
+    /** Sets the listener that handles the change in switch state. */
+    public void setSwitchToggleListener(OnSwitchToggleListener listener) {
+        mToggleListener = listener;
+    }
+
+    /** Gets the listener that handles the change in switch state. */
+    public OnSwitchToggleListener getSwitchToggleListener() {
+        return mToggleListener;
+    }
+
+    @Override
+    protected void onBindWidgetFrame(View widgetFrame) {
+        mSwitch = widgetFrame.findViewById(R.id.master_switch);
+        mSwitch.setChecked(mIsChecked);
+        mSwitch.setOnCheckedChangeListener(mCheckedChangeListener);
+        widgetFrame.setOnClickListener(v -> setSwitchChecked(!mIsChecked));
+    }
+
+    /**
+     * Sets the state of the switch. Can be set even when it isn't visible or bound in order to set
+     * the initial state.
+     */
+    public void setSwitchChecked(boolean checked) {
+        mIsChecked = checked;
+        if (!isActionShown()) {
+            return;
+        }
+
+        if (mSwitch != null) {
+            mSwitch.setChecked(checked);
+        }
+    }
+
+    /** Gets the state of the switch. */
+    public boolean isSwitchChecked() {
+        return mIsChecked;
+    }
+}
diff --git a/src/com/android/car/settings/common/PasswordEditTextPreference.java b/src/com/android/car/settings/common/PasswordEditTextPreference.java
new file mode 100644
index 0000000..bd45544
--- /dev/null
+++ b/src/com/android/car/settings/common/PasswordEditTextPreference.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.car.settings.R;
+
+/**
+ * Extends {@link ValidatedEditTextPreference} for password input. When {@link SettingsFragment}
+ * detects an instance of this class, it creates a new instance of {@link
+ * PasswordEditTextPreferenceDialogFragment} so that the input is obscured on the dialog's TextEdit.
+ */
+public class PasswordEditTextPreference extends ValidatedEditTextPreference {
+
+    public PasswordEditTextPreference(Context context) {
+        super(context);
+        init();
+    }
+
+    public PasswordEditTextPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public PasswordEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    public PasswordEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    private void init() {
+        setDialogLayoutResource(R.layout.preference_dialog_password_edittext);
+        setPersistent(false);
+    }
+}
diff --git a/src/com/android/car/settings/common/PasswordEditTextPreferenceDialogFragment.java b/src/com/android/car/settings/common/PasswordEditTextPreferenceDialogFragment.java
new file mode 100644
index 0000000..982f2c8
--- /dev/null
+++ b/src/com/android/car/settings/common/PasswordEditTextPreferenceDialogFragment.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+
+import com.android.car.settings.R;
+
+/**
+ * Extends {@link ValidatedEditTextPreferenceDialogFragment} for entering password input. Obscures
+ * password input by default and reveals a checkbox that toggles the password visibility.
+ */
+public class PasswordEditTextPreferenceDialogFragment extends
+        ValidatedEditTextPreferenceDialogFragment {
+
+    private EditText mEditText;
+
+    /**
+     * Returns a new instance of {@link PasswordEditTextPreferenceDialogFragment} for the {@link
+     * PasswordEditTextPreference} with the given {@code key}.
+     */
+    public static PasswordEditTextPreferenceDialogFragment newInstance(String key) {
+        PasswordEditTextPreferenceDialogFragment fragment =
+                new PasswordEditTextPreferenceDialogFragment();
+        Bundle b = new Bundle(/* capacity= */ 1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        mEditText = view.findViewById(android.R.id.edit);
+        mEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+        CheckBox cb = view.findViewById(R.id.checkbox);
+        cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (isChecked) {
+                    mEditText.setInputType(InputType.TYPE_CLASS_TEXT
+                            | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+                } else {
+                    mEditText.setInputType(InputType.TYPE_CLASS_TEXT
+                            | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+                }
+                // Place cursor at the end
+                mEditText.setSelection(mEditText.getText().length());
+            }
+        });
+    }
+}
diff --git a/src/com/android/car/settings/common/PreferenceController.java b/src/com/android/car/settings/common/PreferenceController.java
new file mode 100644
index 0000000..b8e0622
--- /dev/null
+++ b/src/com/android/car/settings/common/PreferenceController.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener;
+import android.content.Context;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Controller which encapsulates the business logic associated with a {@link Preference}. All car
+ * settings controllers should extend this class.
+ *
+ * <p>Controllers are responsible for populating and modifying the presentation of an associated
+ * preference while responding to changes in system state. This is enabled via {@link
+ * SettingsFragment} which registers controllers as observers on its lifecycle and dispatches
+ * {@link CarUxRestrictions} change events to the controllers via the {@link
+ * OnUxRestrictionsChangedListener} interface.
+ *
+ * <p>Controllers should be instantiated from XML. To do so, define a preference and include the
+ * {@code controller} attribute in the preference tag and assign the fully qualified class name.
+ *
+ * <p>For example:
+ * <pre>{@code
+ * <Preference
+ *     android:key="my_preference_key"
+ *     android:title="@string/my_preference_title"
+ *     android:icon="@drawable/ic_settings"
+ *     android:fragment="com.android.settings.foo.MyFragment"
+ *     settings:controller="com.android.settings.foo.MyPreferenceController"/>
+ * }</pre>
+ *
+ * <p>Subclasses must implement {@link #getPreferenceType()} to define the upper bound type on the
+ * {@link Preference} that the controller is associated with. For example, a bound of {@link
+ * androidx.preference.PreferenceGroup} indicates that the controller will utilize preference group
+ * methods in its operation. {@link #setPreference(Preference)} will throw an {@link
+ * IllegalArgumentException} if not passed a subclass of the upper bound type.
+ *
+ * <p>Subclasses may implement any or all of the following methods (see method Javadocs for more
+ * information):
+ *
+ * <ul>
+ * <li>{@link #checkInitialized()}
+ * <li>{@link #onCreateInternal()}
+ * <li>{@link #getAvailabilityStatus()}
+ * <li>{@link #onStartInternal()}
+ * <li>{@link #onResumeInternal()}
+ * <li>{@link #onPauseInternal()}
+ * <li>{@link #onStopInternal()}
+ * <li>{@link #onDestroyInternal()}
+ * <li>{@link #updateState(Preference)}
+ * <li>{@link #onApplyUxRestrictions(CarUxRestrictions)}
+ * <li>{@link #handlePreferenceChanged(Preference, Object)}
+ * <li>{@link #handlePreferenceClicked(Preference)}
+ * </ul>
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller
+ *            expects to operate.
+ */
+public abstract class PreferenceController<V extends Preference> implements
+        DefaultLifecycleObserver,
+        OnUxRestrictionsChangedListener {
+
+    /**
+     * Denotes the availability of a setting.
+     *
+     * @see #getAvailabilityStatus()
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({AVAILABLE, CONDITIONALLY_UNAVAILABLE, UNSUPPORTED_ON_DEVICE, DISABLED_FOR_USER})
+    public @interface AvailabilityStatus {
+    }
+
+    /**
+     * The setting is available.
+     */
+    public static final int AVAILABLE = 0;
+
+    /**
+     * The setting is currently unavailable but may become available in the future. Use
+     * {@link #DISABLED_FOR_USER} if it describes the condition more accurately.
+     */
+    public static final int CONDITIONALLY_UNAVAILABLE = 1;
+
+    /**
+     * The setting is not and will not be supported by this device.
+     */
+    public static final int UNSUPPORTED_ON_DEVICE = 2;
+
+    /**
+     * The setting cannot be changed by the current user.
+     */
+    public static final int DISABLED_FOR_USER = 3;
+
+    private final Context mContext;
+    private final String mPreferenceKey;
+    private final FragmentController mFragmentController;
+
+    private CarUxRestrictions mUxRestrictions;
+    private V mPreference;
+    private boolean mIsCreated;
+
+    /**
+     * Controllers should be instantiated from XML. To pass additional arguments see
+     * {@link SettingsFragment#use(Class, int)}.
+     */
+    public PreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        mContext = context;
+        mPreferenceKey = preferenceKey;
+        mFragmentController = fragmentController;
+        mUxRestrictions = uxRestrictions;
+    }
+
+    /**
+     * Returns the context used to construct the controller.
+     */
+    protected final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns the key for the preference managed by this controller set at construction.
+     */
+    protected final String getPreferenceKey() {
+        return mPreferenceKey;
+    }
+
+    /**
+     * Returns the {@link FragmentController} used to launch fragments and go back to previous
+     * fragments. This is set at construction.
+     */
+    protected final FragmentController getFragmentController() {
+        return mFragmentController;
+    }
+
+    /**
+     * Returns the current {@link CarUxRestrictions} applied to the controller. Subclasses may use
+     * this to limit which content is displayed in the associated preference. May be called anytime.
+     */
+    protected final CarUxRestrictions getUxRestrictions() {
+        return mUxRestrictions;
+    }
+
+    /**
+     * Returns the preference associated with this controller. This may be used in any of the
+     * lifecycle methods, as the preference is set before they are called..
+     */
+    protected final V getPreference() {
+        return mPreference;
+    }
+
+    /**
+     * Called by {@link SettingsFragment} to associate the controller with its preference after the
+     * screen is created. This is guaranteed to be called before {@link #onCreateInternal()}.
+     *
+     * @throws IllegalArgumentException if the given preference does not match the type
+     *                                  returned by {@link #getPreferenceType()}
+     * @throws IllegalStateException    if subclass defined initialization is not
+     *                                  complete.
+     */
+    final void setPreference(Preference preference) {
+        PreferenceUtil.requirePreferenceType(preference, getPreferenceType());
+        mPreference = getPreferenceType().cast(preference);
+        mPreference.setOnPreferenceChangeListener(
+                (changedPref, newValue) -> handlePreferenceChanged(
+                        getPreferenceType().cast(changedPref), newValue));
+        mPreference.setOnPreferenceClickListener(
+                clickedPref -> handlePreferenceClicked(getPreferenceType().cast(clickedPref)));
+        checkInitialized();
+    }
+
+    /**
+     * Called by {@link SettingsFragment} to notify that the applied ux restrictions have changed.
+     * The controller will refresh its UI accordingly unless it is not yet created. In that case,
+     * the UI will refresh once created.
+     */
+    @Override
+    public final void onUxRestrictionsChanged(CarUxRestrictions uxRestrictions) {
+        mUxRestrictions = uxRestrictions;
+        refreshUi();
+    }
+
+    /**
+     * Updates the preference presentation based on its {@link #getAvailabilityStatus()} status. If
+     * the controller is available, the associated preference is shown and a call to {@link
+     * #updateState(Preference)} and {@link #onApplyUxRestrictions(CarUxRestrictions)} are
+     * dispatched to allow the controller to modify the presentation for the current state. If the
+     * controller is not available, the associated preference is hidden from the screen. This is a
+     * no-op if the controller is not yet created.
+     */
+    public final void refreshUi() {
+        if (!mIsCreated) {
+            return;
+        }
+        if (getAvailabilityStatus() == AVAILABLE) {
+            mPreference.setVisible(true);
+            mPreference.setEnabled(true);
+            updateState(mPreference);
+            onApplyUxRestrictions(mUxRestrictions);
+        } else {
+            mPreference.setVisible(false);
+        }
+    }
+
+    // Controller lifecycle ========================================================================
+
+    /**
+     * Dispatches a call to {@link #onCreateInternal()} and {@link #refreshUi()} to enable
+     * controllers to setup initial state before a preference is visible. If the controller is
+     * {@link #UNSUPPORTED_ON_DEVICE}, the preference is hidden and no further action is taken.
+     */
+    @Override
+    public final void onCreate(@NonNull LifecycleOwner owner) {
+        if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) {
+            mPreference.setVisible(false);
+            return;
+        }
+        onCreateInternal();
+        mIsCreated = true;
+        refreshUi();
+    }
+
+    /**
+     * Dispatches a call to {@link #onStartInternal()} and {@link #refreshUi()} to account for any
+     * state changes that may have occurred while the controller was stopped. Returns immediately
+     * if the controller is {@link #UNSUPPORTED_ON_DEVICE}.
+     */
+    @Override
+    public final void onStart(@NonNull LifecycleOwner owner) {
+        if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) {
+            return;
+        }
+        onStartInternal();
+        refreshUi();
+    }
+
+    /**
+     * Notifies that the controller is resumed by dispatching a call to {@link #onResumeInternal()}.
+     * Returns immediately if the controller is {@link #UNSUPPORTED_ON_DEVICE}.
+     */
+    @Override
+    public final void onResume(@NonNull LifecycleOwner owner) {
+        if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) {
+            return;
+        }
+        onResumeInternal();
+    }
+
+    /**
+     * Notifies that the controller is paused by dispatching a call to {@link #onPauseInternal()}.
+     * Returns immediately if the controller is {@link #UNSUPPORTED_ON_DEVICE}.
+     */
+    @Override
+    public final void onPause(@NonNull LifecycleOwner owner) {
+        if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) {
+            return;
+        }
+        onPauseInternal();
+    }
+
+    /**
+     * Notifies that the controller is stopped by dispatching a call to {@link #onStopInternal()}.
+     * Returns immediately if the controller is {@link #UNSUPPORTED_ON_DEVICE}.
+     */
+    @Override
+    public final void onStop(@NonNull LifecycleOwner owner) {
+        if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) {
+            return;
+        }
+        onStopInternal();
+    }
+
+    /**
+     * Notifies that the controller is destroyed by dispatching a call to {@link
+     * #onDestroyInternal()}. Returns immediately if the controller is
+     * {@link #UNSUPPORTED_ON_DEVICE}.
+     */
+    @Override
+    public final void onDestroy(@NonNull LifecycleOwner owner) {
+        if (getAvailabilityStatus() == UNSUPPORTED_ON_DEVICE) {
+            return;
+        }
+        mIsCreated = false;
+        onDestroyInternal();
+    }
+
+    // Methods for override ========================================================================
+
+    /**
+     * Returns the upper bound type of the preference on which this controller will operate.
+     */
+    protected abstract Class<V> getPreferenceType();
+
+    /**
+     * Subclasses may override this method to throw {@link IllegalStateException} if any expected
+     * post-instantiation setup is not completed using {@link SettingsFragment#use(Class, int)}
+     * prior to associating the controller with its preference. This will be called before the
+     * controller lifecycle begins.
+     */
+    protected void checkInitialized() {
+    }
+
+    /**
+     * Returns the {@link AvailabilityStatus} for the setting. This status is used to determine
+     * if the setting should be shown or hidden. Defaults to {@link #AVAILABLE}. This will be
+     * called before the controller lifecycle begins and on refresh events.
+     */
+    @AvailabilityStatus
+    protected int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    /**
+     * Subclasses may override this method to complete any operations needed at creation time e.g.
+     * loading static configuration.
+     *
+     * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers.
+     */
+    protected void onCreateInternal() {
+    }
+
+    /**
+     * Subclasses may override this method to complete any operations needed each time the
+     * controller is started e.g. registering broadcast receivers.
+     *
+     * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers.
+     */
+    protected void onStartInternal() {
+    }
+
+    /**
+     * Subclasses may override this method to complete any operations needed each time the
+     * controller is resumed. Prefer to use {@link #onStartInternal()} unless absolutely necessary
+     * as controllers may not be resumed in a multi-display scenario.
+     *
+     * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers.
+     */
+    protected void onResumeInternal() {
+    }
+
+    /**
+     * Subclasses may override this method to complete any operations needed each time the
+     * controller is paused. Prefer to use {@link #onStartInternal()} unless absolutely necessary
+     * as controllers may not be resumed in a multi-display scenario.
+     *
+     * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers.
+     */
+    protected void onPauseInternal() {
+    }
+
+    /**
+     * Subclasses may override this method to complete any operations needed each time the
+     * controller is stopped e.g. unregistering broadcast receivers.
+     *
+     * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers.
+     */
+    protected void onStopInternal() {
+    }
+
+    /**
+     * Subclasses may override this method to complete any operations needed when the controller is
+     * destroyed e.g. freeing up held resources.
+     *
+     * <p>Note: this will not be called on {@link #UNSUPPORTED_ON_DEVICE} controllers.
+     */
+    protected void onDestroyInternal() {
+    }
+
+    /**
+     * Subclasses may override this method to update the presentation of the preference for the
+     * current system state (summary, switch state, etc). If the preference has dynamic content
+     * (such as preferences added to a group), it may be updated here as well.
+     *
+     * <p>Important: Operations should be idempotent as this may be called multiple times.
+     *
+     * <p>Note: this will only be called when the following are true:
+     * <ul>
+     * <li>{@link #getAvailabilityStatus()} returns {@link #AVAILABLE}
+     * <li>{@link #onCreateInternal()} has completed.
+     * </ul>
+     */
+    protected void updateState(V preference) {
+    }
+
+    /**
+     * Updates the preference enabled status given the {@code restrictionInfo}. This will be called
+     * before the controller lifecycle begins and on refresh events. The preference is disabled by
+     * default when {@link CarUxRestrictions#UX_RESTRICTIONS_NO_SETUP} is set in {@code
+     * uxRestrictions}. Subclasses may override this method to modify enabled state based on
+     * additional driving restrictions.
+     */
+    protected void onApplyUxRestrictions(CarUxRestrictions uxRestrictions) {
+        if (CarUxRestrictionsHelper.isNoSetup(uxRestrictions)) {
+            mPreference.setEnabled(false);
+        }
+    }
+
+    /**
+     * Called when the associated preference is changed by the user. This is called before the state
+     * of the preference is updated and before the state is persisted.
+     *
+     * @param preference the changed preference.
+     * @param newValue   the new value of the preference.
+     * @return {@code true} to update the state of the preference with the new value. Defaults to
+     * {@code true}.
+     */
+    protected boolean handlePreferenceChanged(V preference, Object newValue) {
+        return true;
+    }
+
+    /**
+     * Called when the preference associated with this controller is clicked. Subclasses may
+     * choose to handle the click event.
+     *
+     * @param preference the clicked preference.
+     * @return {@code true} if click is handled and further propagation should cease. Defaults to
+     * {@code false}.
+     */
+    protected boolean handlePreferenceClicked(V preference) {
+        return false;
+    }
+}
diff --git a/src/com/android/car/settings/common/PreferenceControllerListHelper.java b/src/com/android/car/settings/common/PreferenceControllerListHelper.java
new file mode 100644
index 0000000..2c1360b
--- /dev/null
+++ b/src/com/android/car/settings/common/PreferenceControllerListHelper.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import static com.android.car.settings.common.PreferenceXmlParser.METADATA_CONTROLLER;
+import static com.android.car.settings.common.PreferenceXmlParser.METADATA_KEY;
+
+import android.annotation.NonNull;
+import android.annotation.XmlRes;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper to load {@link PreferenceController} instances from XML. Based on com.android
+ * .settings.core.PreferenceControllerListHelper.
+ */
+class PreferenceControllerListHelper {
+    private PreferenceControllerListHelper() {
+    }
+
+    /**
+     * Creates a list of {@link PreferenceController}.
+     *
+     * @param context the {@link Context} used to instantiate the controllers.
+     * @param xmlResId the XML resource containing the metadata of the controllers to
+     *         create.
+     * @param fragmentController a valid {@link FragmentController} the preference
+     *         controllers can use to navigate.
+     * @param uxRestrictions the current {@link CarUxRestrictions}.
+     * @throws IllegalArgumentException if the XML resource cannot be parsed, if the XML
+     *         resource contains elements which declare controllers without preference keys, if the
+     *         XML resource contains controllers which cannot be instantiated successfully.
+     */
+    @NonNull
+    static List<PreferenceController> getPreferenceControllersFromXml(Context context,
+            @XmlRes int xmlResId, FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        List<PreferenceController> controllers = new ArrayList<>();
+        List<Bundle> preferenceMetadata;
+        try {
+            preferenceMetadata = PreferenceXmlParser.extractMetadata(context, xmlResId,
+                    PreferenceXmlParser.MetadataFlag.FLAG_NEED_KEY
+                            | PreferenceXmlParser.MetadataFlag.FLAG_NEED_PREF_CONTROLLER);
+        } catch (IOException | XmlPullParserException e) {
+            throw new IllegalArgumentException(
+                    "Failed to parse preference XML for getting controllers", e);
+        }
+
+        for (Bundle metadata : preferenceMetadata) {
+            String controllerName = metadata.getString(METADATA_CONTROLLER);
+            if (TextUtils.isEmpty(controllerName)) {
+                continue; // Preference does not require a controller.
+            }
+            String key = metadata.getString(METADATA_KEY);
+            if (TextUtils.isEmpty(key)) {
+                throw new IllegalArgumentException("Missing key for controller: " + controllerName);
+            }
+            controllers.add(createInstance(controllerName, context, key, fragmentController,
+                    uxRestrictions));
+        }
+
+        return controllers;
+    }
+
+    private static PreferenceController createInstance(String controllerName,
+            Context context, String key, FragmentController fragmentController,
+            CarUxRestrictions restrictionInfo) {
+        try {
+            Class<?> clazz = Class.forName(controllerName);
+            Constructor<?> preferenceConstructor = clazz.getConstructor(Context.class, String.class,
+                    FragmentController.class, CarUxRestrictions.class);
+            Object[] params = new Object[]{context, key, fragmentController, restrictionInfo};
+            return (PreferenceController) preferenceConstructor.newInstance(params);
+        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException
+                | InvocationTargetException | IllegalAccessException e) {
+            throw new IllegalArgumentException(
+                    "Invalid preference controller: " + controllerName, e);
+        }
+    }
+}
+
diff --git a/src/com/android/car/settings/common/PreferenceUtil.java b/src/com/android/car/settings/common/PreferenceUtil.java
new file mode 100644
index 0000000..6cfb64c
--- /dev/null
+++ b/src/com/android/car/settings/common/PreferenceUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import androidx.preference.Preference;
+
+/** Contains utility function to operate on Preferences. */
+public class PreferenceUtil {
+
+    /** Ensures that the preference of given type. */
+    public static boolean checkPreferenceType(Preference preference, Class expectedType) {
+        return expectedType.isInstance(preference);
+    }
+
+    /**
+     * Requires that the preference is of given type.
+     *
+     * @throws IllegalArgumentException if the preference is not of the given type.
+     */
+    public static void requirePreferenceType(Preference preference, Class expectedType) {
+        if (!checkPreferenceType(preference, expectedType)) {
+            throw new IllegalArgumentException(
+                    "Preference should be of type " + expectedType.getName());
+        }
+    }
+}
diff --git a/src/com/android/car/settings/common/PreferenceXmlParser.java b/src/com/android/car/settings/common/PreferenceXmlParser.java
new file mode 100644
index 0000000..3e904d3
--- /dev/null
+++ b/src/com/android/car/settings/common/PreferenceXmlParser.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.annotation.NonNull;
+import android.annotation.XmlRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import androidx.annotation.IntDef;
+
+import com.android.car.settings.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utility class to parse elements of XML preferences. This is a reduced version of {@code com
+ * .android.settings.core.PreferenceXmlParserUtils}.
+ */
+public class PreferenceXmlParser {
+
+    private static final Logger LOG = new Logger(PreferenceXmlParser.class);
+
+    private static final String PREF_TAG_ENDS_WITH = "Preference";
+    private static final String PREF_GROUP_TAG_ENDS_WITH = "PreferenceGroup";
+    private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList("Preference",
+            "PreferenceCategory", "PreferenceScreen");
+
+    /**
+     * Flag definition to indicate which metadata should be extracted when
+     * {@link #extractMetadata(Context, int, int)} is called. The flags can be combined by using |
+     * (binary or).
+     */
+    @IntDef(flag = true, value = {
+            MetadataFlag.FLAG_NEED_KEY,
+            MetadataFlag.FLAG_NEED_PREF_CONTROLLER})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MetadataFlag {
+        int FLAG_NEED_KEY = 1;
+        int FLAG_NEED_PREF_CONTROLLER = 1 << 1;
+    }
+
+    static final String METADATA_KEY = "key";
+    static final String METADATA_CONTROLLER = "controller";
+
+    /**
+     * Extracts metadata from each preference XML and puts them into a {@link Bundle}.
+     *
+     * @param xmlResId xml res id of a preference screen
+     * @param flags one or more of {@link MetadataFlag}
+     * @return a list of Bundles containing the extracted metadata
+     */
+    @NonNull
+    public static List<Bundle> extractMetadata(Context context, @XmlRes int xmlResId, int flags)
+            throws IOException, XmlPullParserException {
+        final List<Bundle> metadata = new ArrayList<>();
+        if (xmlResId <= 0) {
+            LOG.d(xmlResId + " is invalid.");
+            return metadata;
+        }
+        final XmlResourceParser parser = context.getResources().getXml(xmlResId);
+
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && type != XmlPullParser.START_TAG) {
+            // Parse next until start tag is found
+        }
+        final int outerDepth = parser.getDepth();
+
+        do {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+            final String nodeName = parser.getName();
+            if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith(PREF_TAG_ENDS_WITH)
+                    && !nodeName.endsWith(PREF_GROUP_TAG_ENDS_WITH)) {
+                continue;
+            }
+            final Bundle preferenceMetadata = new Bundle();
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            final TypedArray preferenceAttributes = context.obtainStyledAttributes(attrs,
+                    R.styleable.Preference);
+
+            if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEY)) {
+                preferenceMetadata.putString(METADATA_KEY, getKey(preferenceAttributes));
+            }
+            if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_CONTROLLER)) {
+                preferenceMetadata.putString(METADATA_CONTROLLER,
+                        getController(preferenceAttributes));
+            }
+            metadata.add(preferenceMetadata);
+
+            preferenceAttributes.recycle();
+        } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth));
+        parser.close();
+
+        return metadata;
+    }
+
+    private static boolean hasFlag(int flags, @MetadataFlag int flag) {
+        return (flags & flag) != 0;
+    }
+
+    private static String getKey(TypedArray styledAttributes) {
+        return styledAttributes.getString(com.android.internal.R.styleable.Preference_key);
+    }
+
+    private static String getController(TypedArray styledAttributes) {
+        return styledAttributes.getString(R.styleable.Preference_controller);
+    }
+}
diff --git a/src/com/android/car/settings/common/ProgressBarPreference.java b/src/com/android/car/settings/common/ProgressBarPreference.java
new file mode 100644
index 0000000..98ab9b6
--- /dev/null
+++ b/src/com/android/car/settings/common/ProgressBarPreference.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.R;
+
+/**
+ * Preference which shows a progress bar. The progress bar layout shown can be changed by setting
+ * the xml layout attribute. The canonical example can be seen in
+ * {@link R.layout#progress_bar_preference}.
+ */
+public class ProgressBarPreference extends Preference {
+
+    private CharSequence mMinLabel;
+    private CharSequence mMaxLabel;
+
+    private int mMin;
+    private int mMax;
+    private int mProgress;
+
+    public ProgressBarPreference(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(attrs);
+    }
+
+    public ProgressBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(attrs);
+    }
+
+    public ProgressBarPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(attrs);
+    }
+
+    public ProgressBarPreference(Context context) {
+        super(context);
+        init(/* attrs= */ null);
+    }
+
+    private void init(AttributeSet attrs) {
+        TypedArray a = getContext().obtainStyledAttributes(attrs,
+                R.styleable.ProgressBarPreference);
+
+        mMin = a.getInteger(R.styleable.ProgressBarPreference_min, 0);
+        mMax = a.getInteger(R.styleable.ProgressBarPreference_max, 100);
+        mProgress = a.getInteger(R.styleable.ProgressBarPreference_progress, 0);
+        mMinLabel = a.getString(R.styleable.ProgressBarPreference_minLabel);
+        mMaxLabel = a.getString(R.styleable.ProgressBarPreference_maxLabel);
+
+        a.recycle();
+    }
+
+    /** Sets the min label of the progress bar. */
+    public void setMinLabel(CharSequence startLabel) {
+        if (mMinLabel != startLabel) {
+            mMinLabel = startLabel;
+            notifyChanged();
+        }
+    }
+
+    /** Sets the max label of the progress bar. */
+    public void setMaxLabel(CharSequence endLabel) {
+        if (mMaxLabel != endLabel) {
+            mMaxLabel = endLabel;
+            notifyChanged();
+        }
+    }
+
+    /** Sets the minimum possible value of the progress bar. */
+    public void setMin(int min) {
+        if (mMin != min) {
+            mMin = min;
+            notifyChanged();
+        }
+    }
+
+    /** Sets the maximum possible value of the progress bar. */
+    public void setMax(int max) {
+        if (mMax != max) {
+            mMax = max;
+            notifyChanged();
+        }
+    }
+
+    /** Sets the current progress value of the progress bar. */
+    public void setProgress(int progress) {
+        if (mProgress != progress) {
+            mProgress = progress;
+            notifyChanged();
+        }
+    }
+
+    /** Returns the current progress value of the progress bar. */
+    public int getProgress() {
+        return mProgress;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+
+        ProgressBar progressBar = (ProgressBar) view.findViewById(android.R.id.progress);
+        progressBar.setMin(mMin);
+        progressBar.setMax(mMax);
+        progressBar.setProgress(mProgress);
+
+        View progressBarLabels = view.findViewById(R.id.progress_bar_labels);
+        if (TextUtils.isEmpty(mMinLabel) && TextUtils.isEmpty(mMaxLabel)) {
+            progressBarLabels.setVisibility(View.GONE);
+        } else {
+            progressBarLabels.setVisibility(View.VISIBLE);
+
+            TextView minLabel = (TextView) view.findViewById(android.R.id.text1);
+            if (minLabel != null) {
+                minLabel.setText(mMinLabel);
+            }
+            TextView maxLabel = (TextView) view.findViewById(android.R.id.text2);
+            if (maxLabel != null) {
+                maxLabel.setText(mMaxLabel);
+            }
+        }
+    }
+}
diff --git a/src/com/android/car/settings/common/SeekBarPreference.java b/src/com/android/car/settings/common/SeekBarPreference.java
new file mode 100644
index 0000000..7ebd9a5
--- /dev/null
+++ b/src/com/android/car/settings/common/SeekBarPreference.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.R;
+
+/**
+ * Car Setting's own version of SeekBarPreference.
+ *
+ * The code is directly taken from androidx.preference.SeekBarPreference. However it has 1 main
+ * functionality difference. There is a new field which can enable continuous updates while the
+ * seek bar value is changing. This can be set programmatically by using the {@link
+ * #setContinuousUpdate() setContinuousUpdate} method.
+ */
+public class SeekBarPreference extends Preference {
+
+    private int mSeekBarValue;
+    private int mMin;
+    private int mMax;
+    private int mSeekBarIncrement;
+    private boolean mTrackingTouch;
+    private SeekBar mSeekBar;
+    private TextView mSeekBarValueTextView;
+    private boolean mAdjustable; // whether the seekbar should respond to the left/right keys
+    private boolean mShowSeekBarValue; // whether to show the seekbar value TextView next to the bar
+    private boolean mContinuousUpdate; // whether scrolling provides continuous calls to listener
+
+    private static final String TAG = "SeekBarPreference";
+
+    /**
+     * Listener reacting to the SeekBar changing value by the user
+     */
+    private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
+            new SeekBar.OnSeekBarChangeListener() {
+                @Override
+                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                    if (fromUser && (mContinuousUpdate || !mTrackingTouch)) {
+                        syncValueInternal(seekBar);
+                    }
+                }
+
+                @Override
+                public void onStartTrackingTouch(SeekBar seekBar) {
+                    mTrackingTouch = true;
+                }
+
+                @Override
+                public void onStopTrackingTouch(SeekBar seekBar) {
+                    mTrackingTouch = false;
+                    if (seekBar.getProgress() + mMin != mSeekBarValue) {
+                        syncValueInternal(seekBar);
+                    }
+                }
+            };
+
+    /**
+     * Listener reacting to the user pressing DPAD left/right keys if {@code
+     * adjustable} attribute is set to true; it transfers the key presses to the SeekBar
+     * to be handled accordingly.
+     */
+    private View.OnKeyListener mSeekBarKeyListener = new View.OnKeyListener() {
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (event.getAction() != KeyEvent.ACTION_DOWN) {
+                return false;
+            }
+
+            if (!mAdjustable && (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                    || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) {
+                // Right or left keys are pressed when in non-adjustable mode; Skip the keys.
+                return false;
+            }
+
+            // We don't want to propagate the click keys down to the seekbar view since it will
+            // create the ripple effect for the thumb.
+            if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
+                return false;
+            }
+
+            if (mSeekBar == null) {
+                Log.e(TAG, "SeekBar view is null and hence cannot be adjusted.");
+                return false;
+            }
+            return mSeekBar.onKeyDown(keyCode, event);
+        }
+    };
+
+    public SeekBarPreference(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes);
+
+        /**
+         * The ordering of these two statements are important. If we want to set max first, we need
+         * to perform the same steps by changing min/max to max/min as following:
+         * mMax = a.getInt(...) and setMin(...).
+         */
+        mMin = a.getInt(R.styleable.SeekBarPreference_min, 0);
+        setMax(a.getInt(R.styleable.SeekBarPreference_android_max, 100));
+        setSeekBarIncrement(a.getInt(R.styleable.SeekBarPreference_seekBarIncrement, 0));
+        mAdjustable = a.getBoolean(R.styleable.SeekBarPreference_adjustable, true);
+        mShowSeekBarValue = a.getBoolean(R.styleable.SeekBarPreference_showSeekBarValue, true);
+        a.recycle();
+    }
+
+    public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SeekBarPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.seekBarPreferenceStyle);
+    }
+
+    public SeekBarPreference(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+        view.itemView.setOnKeyListener(mSeekBarKeyListener);
+        mSeekBar = (SeekBar) view.findViewById(R.id.seekbar);
+        mSeekBarValueTextView = (TextView) view.findViewById(R.id.seekbar_value);
+        if (mShowSeekBarValue) {
+            mSeekBarValueTextView.setVisibility(View.VISIBLE);
+        } else {
+            mSeekBarValueTextView.setVisibility(View.GONE);
+            mSeekBarValueTextView = null;
+        }
+
+        if (mSeekBar == null) {
+            Log.e(TAG, "SeekBar view is null in onBindViewHolder.");
+            return;
+        }
+        mSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
+        mSeekBar.setMax(mMax - mMin);
+        // If the increment is not zero, use that. Otherwise, use the default mKeyProgressIncrement
+        // in AbsSeekBar when it's zero. This default increment value is set by AbsSeekBar
+        // after calling setMax. That's why it's important to call setKeyProgressIncrement after
+        // calling setMax() since setMax() can change the increment value.
+        if (mSeekBarIncrement != 0) {
+            mSeekBar.setKeyProgressIncrement(mSeekBarIncrement);
+        } else {
+            mSeekBarIncrement = mSeekBar.getKeyProgressIncrement();
+        }
+
+        mSeekBar.setProgress(mSeekBarValue - mMin);
+        if (mSeekBarValueTextView != null) {
+            mSeekBarValueTextView.setText(String.valueOf(mSeekBarValue));
+        }
+        mSeekBar.setEnabled(isEnabled());
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        setValue(restoreValue ? getPersistedInt(mSeekBarValue)
+                : (Integer) defaultValue);
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getInt(index, 0);
+    }
+
+    /** Setter for the minimum value allowed on seek bar. */
+    public void setMin(int min) {
+        if (min > mMax) {
+            min = mMax;
+        }
+        if (min != mMin) {
+            mMin = min;
+            notifyChanged();
+        }
+    }
+
+    /** Getter for the minimum value allowed on seek bar. */
+    public int getMin() {
+        return mMin;
+    }
+
+    /** Setter for the maximum value allowed on seek bar. */
+    public final void setMax(int max) {
+        if (max < mMin) {
+            max = mMin;
+        }
+        if (max != mMax) {
+            mMax = max;
+            notifyChanged();
+        }
+    }
+
+    /**
+     * Returns the amount of increment change via each arrow key click. This value is derived
+     * from
+     * user's specified increment value if it's not zero. Otherwise, the default value is picked
+     * from the default mKeyProgressIncrement value in {@link android.widget.AbsSeekBar}.
+     *
+     * @return The amount of increment on the SeekBar performed after each user's arrow key press.
+     */
+    public final int getSeekBarIncrement() {
+        return mSeekBarIncrement;
+    }
+
+    /**
+     * Sets the increment amount on the SeekBar for each arrow key press.
+     *
+     * @param seekBarIncrement The amount to increment or decrement when the user presses an
+     *                         arrow key.
+     */
+    public final void setSeekBarIncrement(int seekBarIncrement) {
+        if (seekBarIncrement != mSeekBarIncrement) {
+            mSeekBarIncrement = Math.min(mMax - mMin, Math.abs(seekBarIncrement));
+            notifyChanged();
+        }
+    }
+
+    /** Getter for the maximum value allowed on seek bar. */
+    public int getMax() {
+        return mMax;
+    }
+
+    /** Setter for the functionality which allows for changing the values via keyboard arrows. */
+    public void setAdjustable(boolean adjustable) {
+        mAdjustable = adjustable;
+    }
+
+    /** Getter for the functionality which allows for changing the values via keyboard arrows. */
+    public boolean isAdjustable() {
+        return mAdjustable;
+    }
+
+    /** Setter for the functionality which allows for continuous triggering of listener code. */
+    public void setContinuousUpdate(boolean continuousUpdate) {
+        mContinuousUpdate = continuousUpdate;
+    }
+
+    /** Setter for the whether the text should be visible. */
+    public void setShowSeekBarValue(boolean showSeekBarValue) {
+        mShowSeekBarValue = showSeekBarValue;
+    }
+
+    /** Setter for the current value of the seek bar. */
+    public void setValue(int seekBarValue) {
+        setValueInternal(seekBarValue, true);
+    }
+
+    private void setValueInternal(int seekBarValue, boolean notifyChanged) {
+        if (seekBarValue < mMin) {
+            seekBarValue = mMin;
+        }
+        if (seekBarValue > mMax) {
+            seekBarValue = mMax;
+        }
+
+        if (seekBarValue != mSeekBarValue) {
+            mSeekBarValue = seekBarValue;
+            if (mSeekBarValueTextView != null) {
+                mSeekBarValueTextView.setText(String.valueOf(mSeekBarValue));
+            }
+            persistInt(seekBarValue);
+            if (notifyChanged) {
+                notifyChanged();
+            }
+        }
+    }
+
+    /** Getter for the current value of the seek bar. */
+    public int getValue() {
+        return mSeekBarValue;
+    }
+
+    /**
+     * Persist the seekBar's seekbar value if callChangeListener
+     * returns true, otherwise set the seekBar's value to the stored value
+     */
+    private void syncValueInternal(SeekBar seekBar) {
+        int seekBarValue = mMin + seekBar.getProgress();
+        if (seekBarValue != mSeekBarValue) {
+            if (callChangeListener(seekBarValue)) {
+                setValueInternal(seekBarValue, false);
+            } else {
+                seekBar.setProgress(mSeekBarValue - mMin);
+            }
+        }
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (isPersistent()) {
+            // No need to save instance state since it's persistent
+            return superState;
+        }
+
+        // Save the instance state
+        final SeekBarPreference.SavedState myState = new SeekBarPreference.SavedState(superState);
+        myState.mSeekBarValue = mSeekBarValue;
+        myState.mMin = mMin;
+        myState.mMax = mMax;
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (!state.getClass().equals(SeekBarPreference.SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        // Restore the instance state
+        SeekBarPreference.SavedState myState = (SeekBarPreference.SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        mSeekBarValue = myState.mSeekBarValue;
+        mMin = myState.mMin;
+        mMax = myState.mMax;
+        notifyChanged();
+    }
+
+    /**
+     * SavedState, a subclass of {@link BaseSavedState}, will store the state
+     * of MyPreference, a subclass of Preference.
+     * <p>
+     * It is important to always call through to super methods.
+     */
+    private static class SavedState extends BaseSavedState {
+        int mSeekBarValue;
+        int mMin;
+        int mMax;
+
+        SavedState(Parcel source) {
+            super(source);
+
+            // Restore the click counter
+            mSeekBarValue = source.readInt();
+            mMin = source.readInt();
+            mMax = source.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+
+            // Save the click counter
+            dest.writeInt(mSeekBarValue);
+            dest.writeInt(mMin);
+            dest.writeInt(mMax);
+        }
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @SuppressWarnings("unused")
+        public static final Parcelable.Creator<SeekBarPreference.SavedState> CREATOR =
+                new Parcelable.Creator<SeekBarPreference.SavedState>() {
+                    @Override
+                    public SeekBarPreference.SavedState createFromParcel(Parcel in) {
+                        return new SeekBarPreference.SavedState(in);
+                    }
+
+                    @Override
+                    public SeekBarPreference.SavedState[] newArray(int size) {
+                        return new SeekBarPreference
+                                .SavedState[size];
+                    }
+                };
+    }
+}
diff --git a/src/com/android/car/settings/common/SettingsFragment.java b/src/com/android/car/settings/common/SettingsFragment.java
new file mode 100644
index 0000000..66b43d3
--- /dev/null
+++ b/src/com/android/car/settings/common/SettingsFragment.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.car.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base fragment for all settings. Subclasses must provide a resource id via
+ * {@link #getPreferenceScreenResId()} for the XML resource which defines the preferences to
+ * display and controllers to update their state. This class is responsible for displaying the
+ * preferences, creating {@link PreferenceController} instances from the metadata, and
+ * associating the preferences with their corresponding controllers.
+ *
+ * <p>{@code preferenceTheme} must be specified in the application theme, and the parent to which
+ * this fragment attaches must implement {@link UxRestrictionsProvider} and
+ * {@link FragmentController} or an {@link IllegalStateException} will be thrown during
+ * {@link #onAttach(Context)}. Changes to driving state restrictions are propagated to
+ * controllers.
+ */
+public abstract class SettingsFragment extends PreferenceFragmentCompat implements
+        CarUxRestrictionsManager.OnUxRestrictionsChangedListener, FragmentController {
+
+    @VisibleForTesting
+    static final String DIALOG_FRAGMENT_TAG =
+            "com.android.car.settings.common.SettingsFragment.DIALOG";
+
+    private static final int MAX_NUM_PENDING_ACTIVITY_RESULT_CALLBACKS = 0xff - 1;
+
+    private final Map<Class, List<PreferenceController>> mPreferenceControllersLookup =
+            new ArrayMap<>();
+    private final List<PreferenceController> mPreferenceControllers = new ArrayList<>();
+    private final SparseArray<ActivityResultCallback> mActivityResultCallbackMap =
+            new SparseArray<>();
+
+    private CarUxRestrictions mUxRestrictions;
+    private int mCurrentRequestIndex = 0;
+
+    /**
+     * Returns the resource id for the preference XML of this fragment.
+     */
+    @XmlRes
+    protected abstract int getPreferenceScreenResId();
+
+    /**
+     * Returns the layout id to use as the activity action bar. Subclasses should override this
+     * method to customize the action bar layout (e.g. additional buttons, switches, etc.). The
+     * default action bar contains a back button and the title.
+     */
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar;
+    }
+
+    /**
+     * Returns the controller of the given {@code clazz} for the given {@code
+     * preferenceKeyResId}. Subclasses may use this method in {@link #onAttach(Context)} to call
+     * setters on controllers to pass additional arguments after construction.
+     *
+     * <p>For example:
+     * <pre>{@code
+     * @Override
+     * public void onAttach(Context context) {
+     *     super.onAttach(context);
+     *     use(MyPreferenceController.class, R.string.pk_my_key).setMyArg(myArg);
+     * }
+     * }</pre>
+     *
+     * <p>Important: Use judiciously to minimize tight coupling between controllers and fragments.
+     */
+    @SuppressWarnings("unchecked") // Class is used as map key.
+    protected <T extends PreferenceController> T use(Class<T> clazz,
+            @StringRes int preferenceKeyResId) {
+        List<PreferenceController> controllerList = mPreferenceControllersLookup.get(clazz);
+        if (controllerList != null) {
+            String preferenceKey = getString(preferenceKeyResId);
+            for (PreferenceController controller : controllerList) {
+                if (controller.getPreferenceKey().equals(preferenceKey)) {
+                    return (T) controller;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        if (!(getActivity() instanceof UxRestrictionsProvider)) {
+            throw new IllegalStateException("Must attach to a UxRestrictionsProvider");
+        }
+        if (!(getActivity() instanceof FragmentController)) {
+            throw new IllegalStateException("Must attach to a FragmentController");
+        }
+
+        TypedValue tv = new TypedValue();
+        getActivity().getTheme().resolveAttribute(androidx.preference.R.attr.preferenceTheme, tv,
+                true);
+        int theme = tv.resourceId;
+        if (theme == 0) {
+            throw new IllegalStateException("Must specify preferenceTheme in theme");
+        }
+        // Construct a context with the theme as controllers may create new preferences.
+        Context styledContext = new ContextThemeWrapper(getActivity(), theme);
+
+        mUxRestrictions = ((UxRestrictionsProvider) requireActivity()).getCarUxRestrictions();
+        mPreferenceControllers.clear();
+        mPreferenceControllers.addAll(
+                PreferenceControllerListHelper.getPreferenceControllersFromXml(styledContext,
+                        getPreferenceScreenResId(), /* fragmentController= */ this,
+                        mUxRestrictions));
+
+        Lifecycle lifecycle = getLifecycle();
+        mPreferenceControllers.forEach(controller -> {
+            lifecycle.addObserver(controller);
+            mPreferenceControllersLookup.computeIfAbsent(controller.getClass(),
+                    k -> new ArrayList<>(/* initialCapacity= */ 1)).add(controller);
+        });
+    }
+
+    /**
+     * Inflates the preferences from {@link #getPreferenceScreenResId()} and associates the
+     * preference with their corresponding {@link PreferenceController} instances.
+     */
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        @XmlRes int resId = getPreferenceScreenResId();
+        if (resId <= 0) {
+            throw new IllegalStateException(
+                    "Fragment must specify a preference screen resource ID");
+        }
+        addPreferencesFromResource(resId);
+        PreferenceScreen screen = getPreferenceScreen();
+        for (PreferenceController controller : mPreferenceControllers) {
+            controller.setPreference(screen.findPreference(controller.getPreferenceKey()));
+        }
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        FrameLayout actionBarContainer = requireActivity().findViewById(R.id.action_bar);
+        if (actionBarContainer != null) {
+            actionBarContainer.removeAllViews();
+            getLayoutInflater().inflate(getActionBarLayoutId(), actionBarContainer);
+
+            TextView titleView = actionBarContainer.requireViewById(R.id.title);
+            titleView.setText(getPreferenceScreen().getTitle());
+
+            // If the fragment is root, change the back button to settings icon.
+            ImageView imageView = actionBarContainer.requireViewById(R.id.back_button);
+            FragmentManager fragmentManager = requireActivity().getSupportFragmentManager();
+            if (fragmentManager.getBackStackEntryCount() == 1
+                    && fragmentManager.findFragmentByTag("0") != null
+                    && fragmentManager.findFragmentByTag("0").getClass().getName().equals(
+                    getString(R.string.config_settings_hierarchy_root_fragment))) {
+                imageView.setImageResource(R.drawable.ic_launcher_settings);
+                imageView.setTag(R.id.back_button, R.drawable.ic_launcher_settings);
+            } else {
+                imageView.setTag(R.id.back_button, R.drawable.ic_arrow_back);
+                actionBarContainer.requireViewById(R.id.action_bar_icon_container)
+                        .setOnClickListener(
+                                v -> requireActivity().onBackPressed());
+            }
+        }
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        Lifecycle lifecycle = getLifecycle();
+        mPreferenceControllers.forEach(lifecycle::removeObserver);
+        mActivityResultCallbackMap.clear();
+    }
+
+    /**
+     * Notifies {@link PreferenceController} instances of changes to {@link CarUxRestrictions}.
+     */
+    @Override
+    public void onUxRestrictionsChanged(CarUxRestrictions uxRestrictions) {
+        if (!uxRestrictions.isSameRestrictions(mUxRestrictions)) {
+            mUxRestrictions = uxRestrictions;
+            for (PreferenceController controller : mPreferenceControllers) {
+                controller.onUxRestrictionsChanged(uxRestrictions);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Settings needs to launch custom dialog types in order to extend the Device Default theme.
+     *
+     * @param preference The Preference object requesting the dialog.
+     */
+    @Override
+    public void onDisplayPreferenceDialog(Preference preference) {
+        // check if dialog is already showing
+        if (findDialogByTag(DIALOG_FRAGMENT_TAG) != null) {
+            return;
+        }
+
+        DialogFragment dialogFragment;
+        if (preference instanceof ValidatedEditTextPreference) {
+            if (preference instanceof PasswordEditTextPreference) {
+                dialogFragment = PasswordEditTextPreferenceDialogFragment.newInstance(
+                        preference.getKey());
+            } else {
+                dialogFragment = ValidatedEditTextPreferenceDialogFragment.newInstance(
+                        preference.getKey());
+            }
+        } else if (preference instanceof EditTextPreference) {
+            dialogFragment = EditTextPreferenceDialogFragment.newInstance(preference.getKey());
+        } else if (preference instanceof ListPreference) {
+            dialogFragment = SettingsListPreferenceDialogFragment.newInstance(preference.getKey());
+        } else {
+            throw new IllegalArgumentException(
+                    "Tried to display dialog for unknown preference type. Did you forget to "
+                            + "override onDisplayPreferenceDialog()?");
+        }
+
+        dialogFragment.setTargetFragment(/* fragment= */ this, /* requestCode= */ 0);
+        showDialog(dialogFragment, DIALOG_FRAGMENT_TAG);
+    }
+
+    @Override
+    public void launchFragment(Fragment fragment) {
+        ((FragmentController) requireActivity()).launchFragment(fragment);
+    }
+
+    @Override
+    public void goBack() {
+        requireActivity().onBackPressed();
+    }
+
+    @Override
+    public void showBlockingMessage() {
+        Toast.makeText(getContext(), R.string.restricted_while_driving, Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    public void showDialog(DialogFragment dialogFragment, @Nullable String tag) {
+        dialogFragment.show(getFragmentManager(), tag);
+    }
+
+    @Nullable
+    @Override
+    public DialogFragment findDialogByTag(String tag) {
+        Fragment fragment = getFragmentManager().findFragmentByTag(tag);
+        if (fragment instanceof DialogFragment) {
+            return (DialogFragment) fragment;
+        }
+        return null;
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode,
+            ActivityResultCallback callback) {
+        validateRequestCodeForPreferenceController(requestCode);
+        int requestIndex = allocateRequestIndex(callback);
+        super.startActivityForResult(intent, ((requestIndex + 1) << 8) + (requestCode & 0xff));
+    }
+
+    @Override
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, Bundle options,
+            ActivityResultCallback callback)
+            throws IntentSender.SendIntentException {
+        validateRequestCodeForPreferenceController(requestCode);
+        int requestIndex = allocateRequestIndex(callback);
+        super.startIntentSenderForResult(intent, ((requestIndex + 1) << 8) + (requestCode & 0xff),
+                fillInIntent, flagsMask, flagsValues, /* extraFlags= */ 0, options);
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        int requestIndex = (requestCode >> 8) & 0xff;
+        if (requestIndex != 0) {
+            requestIndex--;
+            ActivityResultCallback callback = mActivityResultCallbackMap.get(requestIndex);
+            mActivityResultCallbackMap.remove(requestIndex);
+            if (callback != null) {
+                callback.processActivityResult(requestCode & 0xff, resultCode, data);
+            }
+        }
+    }
+
+    // Allocates the next available startActivityForResult request index.
+    private int allocateRequestIndex(ActivityResultCallback callback) {
+        // Sanity check that we haven't exhausted the request index space.
+        if (mActivityResultCallbackMap.size() >= MAX_NUM_PENDING_ACTIVITY_RESULT_CALLBACKS) {
+            throw new IllegalStateException(
+                    "Too many pending activity result callbacks.");
+        }
+
+        // Find an unallocated request index in the mPendingFragmentActivityResults map.
+        while (mActivityResultCallbackMap.indexOfKey(mCurrentRequestIndex) >= 0) {
+            mCurrentRequestIndex =
+                    (mCurrentRequestIndex + 1) % MAX_NUM_PENDING_ACTIVITY_RESULT_CALLBACKS;
+        }
+
+        mActivityResultCallbackMap.put(mCurrentRequestIndex, callback);
+        return mCurrentRequestIndex;
+    }
+
+    /**
+     * Checks whether the given request code is a valid code by masking it with 0xff00. Throws an
+     * {@link IllegalArgumentException} if the code is not valid.
+     */
+    private static void validateRequestCodeForPreferenceController(int requestCode) {
+        if ((requestCode & 0xff00) != 0) {
+            throw new IllegalArgumentException("Can only use lower 8 bits for requestCode");
+        }
+    }
+}
diff --git a/src/com/android/car/settings/common/SettingsListPreferenceDialogFragment.java b/src/com/android/car/settings/common/SettingsListPreferenceDialogFragment.java
new file mode 100644
index 0000000..1e32c59
--- /dev/null
+++ b/src/com/android/car/settings/common/SettingsListPreferenceDialogFragment.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+
+/**
+ * Presents a dialog with a list of options associated with a {@link ListPreference}.
+ *
+ * <p>Note: this is borrowed as-is from androidx.preference.EditTextPreferenceDialogFragmentCompat
+ * with updates to formatting to match the project style. CarSettings needs to use custom dialog
+ * implementations in order to launch the platform {@link AlertDialog} instead of the one in the
+ * support library.
+ */
+public class SettingsListPreferenceDialogFragment extends SettingsPreferenceDialogFragment {
+
+    private static final String SAVE_STATE_INDEX = "SettingsListPreferenceDialogFragment.index";
+    private static final String SAVE_STATE_ENTRIES = "SettingsListPreferenceDialogFragment.entries";
+    private static final String SAVE_STATE_ENTRY_VALUES =
+            "SettingsListPreferenceDialogFragment.entryValues";
+
+    private int mClickedDialogEntryIndex;
+    private CharSequence[] mEntries;
+    private CharSequence[] mEntryValues;
+
+    /**
+     * Returns a new instance of {@link SettingsListPreferenceDialogFragment} for the {@link
+     * ListPreference} with the given {@code key}.
+     */
+    public static SettingsListPreferenceDialogFragment newInstance(String key) {
+        SettingsListPreferenceDialogFragment fragment = new SettingsListPreferenceDialogFragment();
+        Bundle b = new Bundle(/* capacity= */ 1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            ListPreference preference = getListPreference();
+
+            if (preference.getEntries() == null || preference.getEntryValues() == null) {
+                throw new IllegalStateException(
+                        "ListPreference requires an entries array and an entryValues array.");
+            }
+
+            mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
+            mEntries = preference.getEntries();
+            mEntryValues = preference.getEntryValues();
+        } else {
+            mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
+            mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
+            mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
+    }
+
+    private ListPreference getListPreference() {
+        return (ListPreference) getPreference();
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+
+        builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
+                (dialog, which) -> {
+                    mClickedDialogEntryIndex = which;
+
+                    // Clicking on an item simulates the positive button click, and dismisses the
+                    // dialog.
+                    SettingsListPreferenceDialogFragment.this.onClick(dialog,
+                            DialogInterface.BUTTON_POSITIVE);
+                    dialog.dismiss();
+                });
+
+        // The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
+        // dialog instead of the user having to press 'Ok'.
+        builder.setPositiveButton(null, null);
+    }
+
+    @Override
+    public void onDialogClosed(boolean positiveResult) {
+        if (positiveResult && mClickedDialogEntryIndex >= 0) {
+            String value = mEntryValues[mClickedDialogEntryIndex].toString();
+            ListPreference preference = getListPreference();
+            if (preference.callChangeListener(value)) {
+                preference.setValue(value);
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/car/settings/common/SettingsPreferenceDialogFragment.java b/src/com/android/car/settings/common/SettingsPreferenceDialogFragment.java
new file mode 100644
index 0000000..01ca5c6
--- /dev/null
+++ b/src/com/android/car/settings/common/SettingsPreferenceDialogFragment.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.preference.DialogPreference;
+import androidx.preference.PreferenceFragmentCompat;
+
+/**
+ * Abstract base class which presents a dialog associated with a {@link
+ * androidx.preference.DialogPreference}. Since the preference object may not be available during
+ * fragment re-creation, the necessary information for displaying the dialog is read once during
+ * the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved instance state.
+ * Custom subclasses should also follow this pattern.
+ *
+ * <p>Note: this is borrowed as-is from androidx.preference.PreferenceDialogFragmentCompat with
+ * updates to formatting to match the project style. CarSettings needs to use custom dialog
+ * implementations in order to launch the platform {@link AlertDialog} instead of the one in the
+ * support library.
+ */
+public abstract class SettingsPreferenceDialogFragment extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    protected static final String ARG_KEY = "key";
+
+    private static final String SAVE_STATE_TITLE = "SettingsPreferenceDialogFragment.title";
+    private static final String SAVE_STATE_POSITIVE_TEXT =
+            "SettingsPreferenceDialogFragment.positiveText";
+    private static final String SAVE_STATE_NEGATIVE_TEXT =
+            "SettingsPreferenceDialogFragment.negativeText";
+    private static final String SAVE_STATE_MESSAGE = "SettingsPreferenceDialogFragment.message";
+    private static final String SAVE_STATE_LAYOUT = "SettingsPreferenceDialogFragment.layout";
+    private static final String SAVE_STATE_ICON = "SettingsPreferenceDialogFragment.icon";
+
+    private DialogPreference mPreference;
+
+    private CharSequence mDialogTitle;
+    private CharSequence mPositiveButtonText;
+    private CharSequence mNegativeButtonText;
+    private CharSequence mDialogMessage;
+    @LayoutRes
+    private int mDialogLayoutRes;
+
+    private BitmapDrawable mDialogIcon;
+
+    /** Which button was clicked. */
+    private int mWhichButtonClicked;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Fragment rawFragment = getTargetFragment();
+        if (!(rawFragment instanceof DialogPreference.TargetFragment)) {
+            throw new IllegalStateException(
+                    "Target fragment must implement TargetFragment interface");
+        }
+
+        DialogPreference.TargetFragment fragment =
+                (DialogPreference.TargetFragment) rawFragment;
+
+        String key = getArguments().getString(ARG_KEY);
+        if (savedInstanceState == null) {
+            mPreference = (DialogPreference) fragment.findPreference(key);
+            mDialogTitle = mPreference.getDialogTitle();
+            mPositiveButtonText = mPreference.getPositiveButtonText();
+            mNegativeButtonText = mPreference.getNegativeButtonText();
+            mDialogMessage = mPreference.getDialogMessage();
+            mDialogLayoutRes = mPreference.getDialogLayoutResource();
+
+            Drawable icon = mPreference.getDialogIcon();
+            if (icon == null || icon instanceof BitmapDrawable) {
+                mDialogIcon = (BitmapDrawable) icon;
+            } else {
+                Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+                        icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+                Canvas canvas = new Canvas(bitmap);
+                icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+                icon.draw(canvas);
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        } else {
+            mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
+            mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
+            mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
+            mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
+            mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
+            Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
+            if (bitmap != null) {
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
+        outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
+        outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
+        outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
+        outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
+        if (mDialogIcon != null) {
+            outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
+        }
+    }
+
+    @Override
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        Context context = getActivity();
+        mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(context)
+                .setTitle(mDialogTitle)
+                .setIcon(mDialogIcon)
+                .setPositiveButton(mPositiveButtonText, this)
+                .setNegativeButton(mNegativeButtonText, this);
+
+        View contentView = onCreateDialogView(context);
+        if (contentView != null) {
+            onBindDialogView(contentView);
+            builder.setView(contentView);
+        } else {
+            builder.setMessage(mDialogMessage);
+        }
+
+        onPrepareDialogBuilder(builder);
+
+        // Create the dialog
+        Dialog dialog = builder.create();
+        if (needInputMethod()) {
+            // Request input only after the dialog is shown. This is to prevent an issue where the
+            // dialog view collapsed the content on small displays.
+            dialog.setOnShowListener(d -> requestInputMethod(dialog));
+        }
+
+        return dialog;
+    }
+
+    /**
+     * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
+     * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
+     *
+     * @return the {@link DialogPreference} associated with this dialog.
+     */
+    public DialogPreference getPreference() {
+        if (mPreference == null) {
+            String key = getArguments().getString(ARG_KEY);
+            DialogPreference.TargetFragment fragment =
+                    (DialogPreference.TargetFragment) getTargetFragment();
+            mPreference = (DialogPreference) fragment.findPreference(key);
+        }
+        return mPreference;
+    }
+
+    /**
+     * Prepares the dialog builder to be shown when the preference is clicked. Use this to set
+     * custom properties on the dialog.
+     *
+     * <p>Do not {@link AlertDialog.Builder#create()} or {@link AlertDialog.Builder#show()}.
+     */
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+    }
+
+    /**
+     * Returns whether the preference needs to display a soft input method when the dialog is
+     * displayed. Default is false. Subclasses should override this method if they need the soft
+     * input method brought up automatically.
+     *
+     * <p>Note: Ensure your subclass manually requests focus (ideally in {@link
+     * #onBindDialogView(View)}) for the input field in order to
+     * correctly attach the input method to the field.
+     */
+    protected boolean needInputMethod() {
+        return false;
+    }
+
+    /**
+     * Sets the required flags on the dialog window to enable input method window to show up.
+     */
+    private void requestInputMethod(Dialog dialog) {
+        Window window = dialog.getWindow();
+        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+    }
+
+    /**
+     * Creates the content view for the dialog (if a custom content view is required). By default,
+     * it inflates the dialog layout resource if it is set.
+     *
+     * @return the content View for the dialog.
+     * @see DialogPreference#setLayoutResource(int)
+     */
+    protected View onCreateDialogView(Context context) {
+        int resId = mDialogLayoutRes;
+        if (resId == 0) {
+            return null;
+        }
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        return inflater.inflate(resId, null);
+    }
+
+    /**
+     * Binds views in the content View of the dialog to data.
+     *
+     * <p>Make sure to call through to the superclass implementation.
+     *
+     * @param view the content View of the dialog, if it is custom.
+     */
+    @CallSuper
+    protected void onBindDialogView(View view) {
+        View dialogMessageView = view.findViewById(android.R.id.message);
+
+        if (dialogMessageView != null) {
+            CharSequence message = mDialogMessage;
+            int newVisibility = View.GONE;
+
+            if (!TextUtils.isEmpty(message)) {
+                if (dialogMessageView instanceof TextView) {
+                    ((TextView) dialogMessageView).setText(message);
+                }
+
+                newVisibility = View.VISIBLE;
+            }
+
+            if (dialogMessageView.getVisibility() != newVisibility) {
+                dialogMessageView.setVisibility(newVisibility);
+            }
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        mWhichButtonClicked = which;
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        super.onDismiss(dialog);
+        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
+    }
+
+    /**
+     * Called when the dialog is dismissed.
+     *
+     * @param positiveResult {@code true} if the dialog was dismissed with {@link
+     *                       DialogInterface#BUTTON_POSITIVE}.
+     */
+    protected abstract void onDialogClosed(boolean positiveResult);
+}
diff --git a/src/com/android/car/settings/common/TwoActionPreference.java b/src/com/android/car/settings/common/TwoActionPreference.java
new file mode 100644
index 0000000..73ee0b9
--- /dev/null
+++ b/src/com/android/car/settings/common/TwoActionPreference.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.R;
+
+/**
+ * A preference which can perform two actions. The secondary action is shown by default.
+ * {@link #showAction(boolean)} may be used to manually set the visibility of the action.
+ */
+public abstract class TwoActionPreference extends Preference {
+
+    private boolean mIsActionShown;
+
+    public TwoActionPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(attrs);
+    }
+
+    public TwoActionPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(attrs);
+    }
+
+    public TwoActionPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(attrs);
+    }
+
+    public TwoActionPreference(Context context) {
+        super(context);
+        init(/* attrs= */ null);
+    }
+
+    private void init(AttributeSet attrs) {
+        setLayoutResource(R.layout.two_action_preference);
+        TypedArray preferenceAttributes = getContext().obtainStyledAttributes(attrs,
+                R.styleable.TwoActionPreference);
+        mIsActionShown = preferenceAttributes.getBoolean(
+                R.styleable.TwoActionPreference_actionShown, true);
+    }
+
+    /**
+     * Sets whether the secondary action is visible in the preference.
+     *
+     * @param isShown {@code true} if the secondary action should be shown.
+     */
+    public void showAction(boolean isShown) {
+        mIsActionShown = isShown;
+        notifyChanged();
+    }
+
+    /** Returns {@code true} if action is shown. */
+    public boolean isActionShown() {
+        return mIsActionShown;
+    }
+
+    @Override
+    public final void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        View actionConatiner = holder.findViewById(R.id.action_widget_container);
+        View widgetFrame = holder.findViewById(android.R.id.widget_frame);
+        if (mIsActionShown) {
+            actionConatiner.setVisibility(View.VISIBLE);
+            onBindWidgetFrame(widgetFrame);
+        } else {
+            actionConatiner.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Binds the created View for the second action.
+     *
+     * <p>This is a good place to set properties on any custom view.
+     *
+     * @param widgetFrame The widget frame which controls the 2nd action.
+     */
+    protected abstract void onBindWidgetFrame(View widgetFrame);
+}
diff --git a/src/com/android/car/settings/common/UxRestrictionsProvider.java b/src/com/android/car/settings/common/UxRestrictionsProvider.java
new file mode 100644
index 0000000..65196f9
--- /dev/null
+++ b/src/com/android/car/settings/common/UxRestrictionsProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import android.car.drivingstate.CarUxRestrictions;
+
+/**
+ * Provides current {@link CarUxRestrictions}.
+ */
+public interface UxRestrictionsProvider {
+
+    /**
+     * Returns the current {@link CarUxRestrictions}.
+     */
+    CarUxRestrictions getCarUxRestrictions();
+}
diff --git a/src/com/android/car/settings/common/ValidatedEditTextPreference.java b/src/com/android/car/settings/common/ValidatedEditTextPreference.java
new file mode 100644
index 0000000..3c46b9b
--- /dev/null
+++ b/src/com/android/car/settings/common/ValidatedEditTextPreference.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * Extends {@link EditTextPreference} to add optional {@link Validator} logic. Validator is passed
+ * on to {@link ValidatedEditTextPreferenceDialogFragment} to be attached to its View.
+ */
+public class ValidatedEditTextPreference extends EditTextPreference {
+
+    /** Defines the validation logic used in this preference. */
+    public interface Validator {
+        /** Returns true only if the value provided meets validation criteria. */
+        boolean isTextValid(String value);
+    }
+
+    private Validator mValidator;
+    private int mInputType;
+
+    public ValidatedEditTextPreference(Context context) {
+        super(context);
+    }
+
+    public ValidatedEditTextPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ValidatedEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public ValidatedEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /** Sets the text input type of the summary field. */
+    public void setSummaryInputType(int inputType) {
+        mInputType = inputType;
+        notifyChanged();
+    }
+
+    /** Gets the text input type of the summary field. */
+    public int getSummaryInputType() {
+        return mInputType;
+    }
+
+    /**
+     * Sets the {@link Validator}. Validator has to be set before the Preference is
+     * clicked for it to be attached to the Dialog's EditText.
+     */
+    public void setValidator(Validator validator) {
+        mValidator = validator;
+    }
+
+    /**
+     * Gets the {@link Validator} for {@link EditTextPreferenceDialogFragment} to attach
+     * to its own View.
+     */
+    public Validator getValidator() {
+        return mValidator;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
+        summaryView.setInputType(mInputType);
+    }
+}
diff --git a/src/com/android/car/settings/common/ValidatedEditTextPreferenceDialogFragment.java b/src/com/android/car/settings/common/ValidatedEditTextPreferenceDialogFragment.java
new file mode 100644
index 0000000..8819951
--- /dev/null
+++ b/src/com/android/car/settings/common/ValidatedEditTextPreferenceDialogFragment.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+/**
+ * Adds optional text validation logic to {@link EditTextPreferenceDialogFragment}. Disables
+ * Positive Button and the ability to press Enter to submit the dialog if the input is invalid.
+ * Validator must be provided by {@link ValidatedEditTextPreference} before launching the Dialog
+ * Fragment for it to be attached to its View.
+ */
+public class ValidatedEditTextPreferenceDialogFragment extends
+        EditTextPreferenceDialogFragment implements TextView.OnEditorActionListener {
+
+    private final EditTextWatcher mTextWatcher = new EditTextWatcher();
+
+    private ValidatedEditTextPreference.Validator mValidator;
+    private EditText mEditText;
+
+    /**
+     * Returns a new instance of {@link ValidatedEditTextPreferenceDialogFragment} for the
+     * {@link ValidatedEditTextPreference} with the given {@code key}.
+     */
+    public static ValidatedEditTextPreferenceDialogFragment newInstance(String key) {
+        ValidatedEditTextPreferenceDialogFragment fragment =
+                new ValidatedEditTextPreferenceDialogFragment();
+        Bundle b = new Bundle(/* capacity= */ 1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+        mEditText = view.findViewById(android.R.id.edit);
+        if (getPreference() instanceof ValidatedEditTextPreference) {
+            ValidatedEditTextPreference.Validator validator =
+                    ((ValidatedEditTextPreference) getPreference()).getValidator();
+            if (validator != null) {
+                attachValidatorToView(view, validator);
+            }
+        }
+
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        allowDialogSubmissionOnlyIfValidInput((AlertDialog) getDialog());
+    }
+
+    private void attachValidatorToView(View view, ValidatedEditTextPreference.Validator validator) {
+        mValidator = validator;
+        EditText editText = view.findViewById(android.R.id.edit);
+        if (mValidator != null && editText != null) {
+            editText.removeTextChangedListener(mTextWatcher);
+            editText.addTextChangedListener(mTextWatcher);
+        }
+    }
+
+    private class EditTextWatcher implements TextWatcher {
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int before, int count) {
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            allowDialogSubmissionOnlyIfValidInput((AlertDialog) getDialog());
+        }
+    }
+
+    private void allowDialogSubmissionOnlyIfValidInput(AlertDialog dialog) {
+        if (dialog != null && mValidator != null && mEditText != null) {
+            boolean valid = mValidator.isTextValid(mEditText.getText().toString());
+            dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(valid);
+            setAllowEnterToSubmit(valid);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/datausage/AppDataUsageFragment.java b/src/com/android/car/settings/datausage/AppDataUsageFragment.java
new file mode 100644
index 0000000..2c756e2
--- /dev/null
+++ b/src/com/android/car/settings/datausage/AppDataUsageFragment.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.content.Context;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.format.DateUtils;
+import android.util.Pair;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+import androidx.loader.app.LoaderManager;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.NetworkPolicyEditor;
+
+import java.time.ZonedDateTime;
+import java.util.Iterator;
+
+/**
+ * Screen to display list of applications using the data.
+ */
+public class AppDataUsageFragment extends SettingsFragment {
+
+    private static final Logger LOG = new Logger(AppDataUsageFragment.class);
+
+    private static final String ARG_NETWORK_SUB_ID = "network_sub_id";
+    /** Value to represent that the subscription id hasn't been computed yet. */
+    private static final int SUB_ID_NULL = Integer.MIN_VALUE;
+
+    private AppsNetworkStatsManager mAppsNetworkStatsManager;
+    private NetworkPolicyEditor mPolicyEditor;
+    private NetworkTemplate mNetworkTemplate;
+
+    private Bundle mBundle;
+
+    /**
+     * Creates a new instance of the {@link AppDataUsageFragment}, which shows settings related to
+     * the given {@code subId}.
+     */
+    public static AppDataUsageFragment newInstance(int subId) {
+        AppDataUsageFragment fragment = new AppDataUsageFragment();
+        Bundle args = new Bundle();
+        args.putInt(ARG_NETWORK_SUB_ID, subId);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.app_data_usage_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        int subId = getArguments() != null
+                ? getArguments().getInt(ARG_NETWORK_SUB_ID, SUB_ID_NULL) : SUB_ID_NULL;
+        if (subId == SUB_ID_NULL) {
+            LOG.d("Cannot get the subscription id from arguments. Switching to default "
+                    + "subscription Id: " + subId);
+            SubscriptionManager subscriptionManager = context.getSystemService(
+                    SubscriptionManager.class);
+            subId = DataUsageUtils.getDefaultSubscriptionId(subscriptionManager);
+        }
+        mNetworkTemplate = DataUsageUtils.getMobileNetworkTemplate(telephonyManager, subId);
+        mPolicyEditor = new NetworkPolicyEditor(NetworkPolicyManager.from(context));
+        mAppsNetworkStatsManager = new AppsNetworkStatsManager(getContext());
+        mAppsNetworkStatsManager.registerListener(
+                use(AppDataUsagePreferenceController.class, R.string.pk_app_data_usage_detail));
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mBundle = getBundleForNetworkStats();
+
+        LoaderManager loaderManager = LoaderManager.getInstance(this);
+        mAppsNetworkStatsManager.startLoading(loaderManager, mBundle);
+    }
+
+    private Bundle getBundleForNetworkStats() {
+        long historyStart = System.currentTimeMillis();
+        long historyEnd = historyStart + 1;
+
+        long start = 0;
+        long end = 0;
+
+        boolean hasCycles = false;
+
+        NetworkPolicy policy = mPolicyEditor.getPolicy(mNetworkTemplate);
+        if (policy != null) {
+            Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = NetworkPolicyManager
+                    .cycleIterator(policy);
+            while (it.hasNext()) {
+                Pair<ZonedDateTime, ZonedDateTime> cycle = it.next();
+                start = cycle.first.toInstant().toEpochMilli();
+                end = cycle.second.toInstant().toEpochMilli();
+                hasCycles = true;
+            }
+        }
+
+        if (!hasCycles) {
+            // no policy defined cycles; show entry for each four-week period
+            long cycleEnd = historyEnd;
+            while (cycleEnd > historyStart) {
+                long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
+
+                start = cycleStart;
+                end = cycleEnd;
+                cycleEnd = cycleStart;
+            }
+        }
+
+        return SummaryForAllUidLoader.buildArgs(mNetworkTemplate, start, end);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    Bundle getBundle() {
+        return mBundle;
+    }
+}
diff --git a/src/com/android/car/settings/datausage/AppDataUsagePreferenceController.java b/src/com/android/car/settings/datausage/AppDataUsagePreferenceController.java
new file mode 100644
index 0000000..577a602
--- /dev/null
+++ b/src/com/android/car/settings/datausage/AppDataUsagePreferenceController.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.net.NetworkStats;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.ProgressBarPreference;
+import com.android.settingslib.AppItem;
+import com.android.settingslib.net.UidDetail;
+import com.android.settingslib.net.UidDetailProvider;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+/**
+ * Controller that adds all the applications using the data sorted by the amount of data used. The
+ * first application that used most amount of data will be at the top with progress 100 percentage.
+ * All other progress are calculated relatively.
+ */
+public class AppDataUsagePreferenceController extends
+        PreferenceController<PreferenceGroup> implements AppsNetworkStatsManager.Callback {
+
+    private final UidDetailProvider mUidDetailProvider;
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public AppDataUsagePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mUidDetailProvider = new UidDetailProvider(getContext());
+        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    public void onDataLoaded(@Nullable NetworkStats stats, @Nullable int[] restrictedUids) {
+        List<AppItem> items = new ArrayList<>();
+        long largest = 0;
+
+        List<UserInfo> profiles = mCarUserManagerHelper.getAllUsers();
+        SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
+
+        NetworkStats.Entry entry = null;
+        if (stats != null) {
+            for (int i = 0; i < stats.size(); i++) {
+                entry = stats.getValues(i, entry);
+                long size = aggregateDataUsage(knownItems, items, entry, profiles);
+                largest = Math.max(size, largest);
+            }
+        }
+
+        updateRestrictedState(restrictedUids, knownItems, items, profiles);
+        sortAndAddPreferences(items, largest);
+    }
+
+    private long aggregateDataUsage(SparseArray<AppItem> knownItems, List<AppItem> items,
+            NetworkStats.Entry entry, List<UserInfo> profiles) {
+        int currentUserId = mCarUserManagerHelper.getCurrentProcessUserId();
+
+        // Decide how to collapse items together.
+        int uid = entry.uid;
+
+        int collapseKey;
+        int category;
+        int userId = UserHandle.getUserId(uid);
+
+        if (isUidValid(uid)) {
+            collapseKey = uid;
+            category = AppItem.CATEGORY_APP;
+            return accumulate(collapseKey, knownItems, entry, category, items);
+        }
+
+        if (!UserHandle.isApp(uid)) {
+            collapseKey = android.os.Process.SYSTEM_UID;
+            category = AppItem.CATEGORY_APP;
+            return accumulate(collapseKey, knownItems, entry, category, items);
+        }
+
+        if (profileContainsUserId(profiles, userId) && userId == currentUserId) {
+            // Add to app item.
+            collapseKey = uid;
+            category = AppItem.CATEGORY_APP;
+            return accumulate(collapseKey, knownItems, entry, category, items);
+        }
+
+        if (profileContainsUserId(profiles, userId) && userId != currentUserId) {
+            // Add to a managed user item.
+            int managedKey = UidDetailProvider.buildKeyForUser(userId);
+            long usersLargest = accumulate(managedKey, knownItems, entry, AppItem.CATEGORY_USER,
+                    items);
+            collapseKey = uid;
+            category = AppItem.CATEGORY_APP;
+            long appLargest = accumulate(collapseKey, knownItems, entry, category, items);
+            return Math.max(usersLargest, appLargest);
+        }
+
+        // If it is a removed user add it to the removed users' key.
+        Optional<UserInfo> info = profiles.stream().filter(
+                userInfo -> userInfo.id == userId).findFirst();
+        if (!info.isPresent()) {
+            collapseKey = UID_REMOVED;
+            category = AppItem.CATEGORY_APP;
+        } else {
+            // Add to other user item.
+            collapseKey = UidDetailProvider.buildKeyForUser(userId);
+            category = AppItem.CATEGORY_USER;
+        }
+
+        return accumulate(collapseKey, knownItems, entry, category, items);
+    }
+
+    /**
+     * UID does not belong to a regular app and maybe belongs to a removed application or
+     * application using for tethering traffic.
+     */
+    private boolean isUidValid(int uid) {
+        return !UserHandle.isApp(uid) && (uid == UID_REMOVED || uid == UID_TETHERING);
+    }
+
+    private boolean profileContainsUserId(List<UserInfo> profiles, int userId) {
+        return profiles.stream().anyMatch(userInfo -> userInfo.id == userId);
+    }
+
+    private void updateRestrictedState(@Nullable int[] restrictedUids,
+            SparseArray<AppItem> knownItems, List<AppItem> items, List<UserInfo> profiles) {
+        if (restrictedUids == null) {
+            return;
+        }
+
+        for (int i = 0; i < restrictedUids.length; ++i) {
+            int uid = restrictedUids[i];
+            // Only splice in restricted state for current user or managed users.
+            if (!profileContainsUserId(profiles, uid)) {
+                continue;
+            }
+
+            AppItem item = knownItems.get(uid);
+            if (item == null) {
+                item = new AppItem(uid);
+                item.total = -1;
+                items.add(item);
+                knownItems.put(item.key, item);
+            }
+            item.restricted = true;
+        }
+    }
+
+    private void sortAndAddPreferences(List<AppItem> items, long largest) {
+        Collections.sort(items);
+        for (int i = 0; i < items.size(); i++) {
+            int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0;
+            AppDataUsagePreference preference = new AppDataUsagePreference(getContext(),
+                    items.get(i), percentTotal, mUidDetailProvider);
+            getPreference().addPreference(preference);
+        }
+    }
+
+    /**
+     * Accumulate data usage of a network stats entry for the item mapped by the collapse key.
+     * Creates the item if needed.
+     *
+     * @param collapseKey the collapse key used to map the item.
+     * @param knownItems collection of known (already existing) items.
+     * @param entry the network stats entry to extract data usage from.
+     * @param itemCategory the item is categorized on the list view by this category. Must be
+     */
+    private static long accumulate(int collapseKey, SparseArray<AppItem> knownItems,
+            NetworkStats.Entry entry, int itemCategory, List<AppItem> items) {
+        int uid = entry.uid;
+        AppItem item = knownItems.get(collapseKey);
+        if (item == null) {
+            item = new AppItem(collapseKey);
+            item.category = itemCategory;
+            items.add(item);
+            knownItems.put(item.key, item);
+        }
+        item.addUid(uid);
+        item.total += entry.rxBytes + entry.txBytes;
+        return item.total;
+    }
+
+    private class AppDataUsagePreference extends ProgressBarPreference {
+
+        private final AppItem mItem;
+        private final int mPercent;
+        private UidDetail mDetail;
+
+        AppDataUsagePreference(Context context, AppItem item, int percent,
+                UidDetailProvider provider) {
+            super(context);
+            mItem = item;
+            mPercent = percent;
+            setLayoutResource(R.layout.progress_bar_preference);
+            setKey(String.valueOf(item.key));
+            if (item.restricted && item.total <= 0) {
+                setSummary(R.string.data_usage_app_restricted);
+            } else {
+                CharSequence s = DataUsageUtils.bytesToIecUnits(context, item.total);
+                setSummary(s);
+            }
+            mDetail = provider.getUidDetail(item.key, false /* blocking */);
+            if (mDetail != null) {
+                setAppInfo();
+            } else {
+                ThreadUtils.postOnBackgroundThread(() -> {
+                    mDetail = provider.getUidDetail(mItem.key, true /* blocking */);
+                    ThreadUtils.postOnMainThread(() -> setAppInfo());
+                });
+            }
+        }
+
+        private void setAppInfo() {
+            if (mDetail != null) {
+                setIcon(mDetail.icon);
+                setTitle(mDetail.label);
+                setProgress(mPercent);
+            } else {
+                setIcon(null);
+                setTitle(null);
+            }
+        }
+    }
+}
diff --git a/src/com/android/car/settings/datausage/AppsNetworkStatsManager.java b/src/com/android/car/settings/datausage/AppsNetworkStatsManager.java
new file mode 100644
index 0000000..2fc6cbc
--- /dev/null
+++ b/src/com/android/car/settings/datausage/AppsNetworkStatsManager.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+
+import android.content.Context;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkStats;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+
+import com.android.car.settings.common.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * Class to manage the callbacks needed to calculate network stats for an application.
+ */
+public class AppsNetworkStatsManager {
+
+    /**
+     * Callback that is called once the AppsNetworkStats is loaded.
+     */
+    public interface Callback {
+        /**
+         * Called when the data is successfully loaded from
+         * {@link AppsNetworkStatsManager.AppsNetworkStatsResult}.
+         */
+        void onDataLoaded(@Nullable NetworkStats stats, @Nullable int[] restrictedUids);
+    }
+
+    private static final Logger LOG = new Logger(AppsNetworkStatsManager.class);
+    private static final int NETWORK_STATS_ID = 1;
+
+    private final Context mContext;
+    private final NetworkPolicyManager mNetworkPolicyManager;
+    private final List<AppsNetworkStatsManager.Callback> mAppsNetworkStatsListeners =
+            new ArrayList<>();
+
+    private INetworkStatsSession mStatsSession;
+
+    AppsNetworkStatsManager(Context context) {
+        mContext = context;
+        mNetworkPolicyManager = NetworkPolicyManager.from(context);
+        try {
+            mStatsSession = INetworkStatsService.Stub.asInterface(
+                    ServiceManager.getService(Context.NETWORK_STATS_SERVICE)).openSession();
+        } catch (RemoteException e) {
+            LOG.e("Could not open a network session", e);
+        }
+    }
+
+    /**
+     * Registers a listener that will be notified once the data is loaded.
+     */
+    public void registerListener(AppsNetworkStatsManager.Callback appsNetworkStatsListener) {
+        if (!mAppsNetworkStatsListeners.contains(appsNetworkStatsListener)) {
+            mAppsNetworkStatsListeners.add(appsNetworkStatsListener);
+        }
+    }
+
+    /**
+     * Unregisters the listener.
+     */
+    public void unregisterListener(AppsNetworkStatsManager.Callback appsNetworkStatsListener) {
+        mAppsNetworkStatsListeners.remove(appsNetworkStatsListener);
+    }
+
+    /**
+     * Start calculating the storage stats.
+     */
+    public void startLoading(LoaderManager loaderManager, Bundle bundle) {
+        loaderManager.restartLoader(NETWORK_STATS_ID, bundle, new AppsNetworkStatsResult());
+    }
+
+    private void onAppsNetworkStatsLoaded(NetworkStats stats, int[] restrictedUids) {
+        for (AppsNetworkStatsManager.Callback listener : mAppsNetworkStatsListeners) {
+            listener.onDataLoaded(stats, restrictedUids);
+        }
+    }
+
+    /**
+     * Callback to calculate applications network stats.
+     */
+    private class AppsNetworkStatsResult implements LoaderManager.LoaderCallbacks<NetworkStats> {
+        @Override
+        public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
+            return new SummaryForAllUidLoader(mContext, mStatsSession, args);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
+            int[] restrictedUids = mNetworkPolicyManager.getUidsWithPolicy(
+                    POLICY_REJECT_METERED_BACKGROUND);
+            onAppsNetworkStatsLoaded(data, restrictedUids);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<NetworkStats> loader) {
+            onAppsNetworkStatsLoaded(/* stats= */ null, /* restrictedUids= */ new int[0]);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/datausage/CycleResetDayOfMonthPickerPreferenceController.java b/src/com/android/car/settings/datausage/CycleResetDayOfMonthPickerPreferenceController.java
new file mode 100644
index 0000000..9d0f96a
--- /dev/null
+++ b/src/com/android/car/settings/datausage/CycleResetDayOfMonthPickerPreferenceController.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.text.format.Time;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Preference which opens a {@link UsageCycleResetDayOfMonthPickerDialog} in order to pick the date
+ * on which the data warning/limit cycle should end.
+ */
+public class CycleResetDayOfMonthPickerPreferenceController extends
+        DataWarningAndLimitBasePreferenceController<Preference> implements
+        UsageCycleResetDayOfMonthPickerDialog.ResetDayOfMonthPickedListener {
+
+    private static final String CYCLE_PICKER_DIALOG_TAG = "cycle_picker_dialog_tag";
+
+    public CycleResetDayOfMonthPickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        UsageCycleResetDayOfMonthPickerDialog dialog =
+                (UsageCycleResetDayOfMonthPickerDialog) getFragmentController().findDialogByTag(
+                        CYCLE_PICKER_DIALOG_TAG);
+        if (dialog != null) {
+            dialog.setResetDayOfMonthPickedListener(/* listener= */ this);
+        }
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+
+        UsageCycleResetDayOfMonthPickerDialog dialog =
+                UsageCycleResetDayOfMonthPickerDialog.newInstance(
+                        getNetworkPolicyEditor().getPolicyCycleDay(getNetworkTemplate()));
+        dialog.setResetDayOfMonthPickedListener(/* listener= */ this);
+        getFragmentController().showDialog(dialog, CYCLE_PICKER_DIALOG_TAG);
+        return true;
+    }
+
+    @Override
+    public void onDayOfMonthPicked(int dayOfMonth) {
+        getNetworkPolicyEditor().setPolicyCycleDay(getNetworkTemplate(), dayOfMonth,
+                new Time().timezone);
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataLimitPreferenceController.java b/src/com/android/car/settings/datausage/DataLimitPreferenceController.java
new file mode 100644
index 0000000..c0f208b
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataLimitPreferenceController.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.datausage.UsageBytesThresholdPickerDialog.BytesThresholdPickedListener;
+
+/** Controls setting the data limit threshold. */
+public class DataLimitPreferenceController extends
+        DataWarningAndLimitBasePreferenceController<PreferenceGroup> implements
+        Preference.OnPreferenceChangeListener, ConfirmationDialogFragment.ConfirmListener,
+        Preference.OnPreferenceClickListener {
+
+    @VisibleForTesting
+    static final float LIMIT_BYTES_MULTIPLIER = 1.2f;
+    private static final long GIB_IN_BYTES = 1024 * 1024 * 1024;
+
+    private final BytesThresholdPickedListener mThresholdPickedListener = numBytes -> {
+        getNetworkPolicyEditor().setPolicyLimitBytes(getNetworkTemplate(), numBytes);
+        refreshUi();
+    };
+
+    private TwoStatePreference mEnableDataLimitPreference;
+    private Preference mSetDataLimitPreference;
+
+    public DataLimitPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mEnableDataLimitPreference = (TwoStatePreference) getPreference().findPreference(
+                getContext().getString(R.string.pk_data_set_limit));
+        mEnableDataLimitPreference.setOnPreferenceChangeListener(this);
+        mSetDataLimitPreference = getPreference().findPreference(
+                getContext().getString(R.string.pk_data_limit));
+        mSetDataLimitPreference.setOnPreferenceClickListener(this);
+
+        ConfirmationDialogFragment.resetListeners(
+                (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
+                        ConfirmationDialogFragment.TAG),
+                /* confirmListener= */ this,
+                /* rejectListener= */ null);
+
+        UsageBytesThresholdPickerDialog dialog =
+                (UsageBytesThresholdPickerDialog) getFragmentController().findDialogByTag(
+                        UsageBytesThresholdPickerDialog.TAG);
+        if (dialog != null) {
+            dialog.setBytesThresholdPickedListener(mThresholdPickedListener);
+        }
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        long limitBytes = getNetworkPolicyEditor().getPolicyLimitBytes(getNetworkTemplate());
+
+        if (limitBytes == LIMIT_DISABLED) {
+            mEnableDataLimitPreference.setChecked(false);
+            mSetDataLimitPreference.setSummary(null);
+        } else {
+            mEnableDataLimitPreference.setChecked(true);
+            mSetDataLimitPreference.setSummary(
+                    DataUsageUtils.bytesToIecUnits(getContext(), limitBytes));
+        }
+        mSetDataLimitPreference.setEnabled(mEnableDataLimitPreference.isChecked());
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        boolean enabled = (Boolean) newValue;
+        if (!enabled) {
+            getNetworkPolicyEditor().setPolicyLimitBytes(getNetworkTemplate(), LIMIT_DISABLED);
+            refreshUi();
+            return true;
+        }
+
+        ConfirmationDialogFragment dialogFragment =
+                new ConfirmationDialogFragment.Builder(getContext())
+                        .setTitle(R.string.data_usage_limit_dialog_title)
+                        .setMessage(R.string.data_usage_limit_dialog_mobile)
+                        .setPositiveButton(android.R.string.ok, this)
+                        .setNegativeButton(android.R.string.cancel, null)
+                        .build();
+        getFragmentController().showDialog(dialogFragment, ConfirmationDialogFragment.TAG);
+
+        // This preference is enabled / disabled by ConfirmationDialogFragment.
+        return false;
+    }
+
+    @Override
+    public void onConfirm(@Nullable Bundle arguments) {
+        long warningBytes = getNetworkPolicyEditor().getPolicyWarningBytes(getNetworkTemplate());
+        long minLimitBytes = 0;
+        if (warningBytes != WARNING_DISABLED) {
+            minLimitBytes = (long) (warningBytes * LIMIT_BYTES_MULTIPLIER);
+        }
+
+        long limitBytes = Math.max(5 * GIB_IN_BYTES, minLimitBytes);
+
+        getNetworkPolicyEditor().setPolicyLimitBytes(getNetworkTemplate(), limitBytes);
+        refreshUi();
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        UsageBytesThresholdPickerDialog dialog = UsageBytesThresholdPickerDialog.newInstance(
+                R.string.data_usage_limit_editor_title,
+                getNetworkPolicyEditor().getPolicyLimitBytes(getNetworkTemplate()));
+        dialog.setBytesThresholdPickedListener(mThresholdPickedListener);
+        getFragmentController().showDialog(dialog, UsageBytesThresholdPickerDialog.TAG);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataUsageEntryPreferenceController.java b/src/com/android/car/settings/datausage/DataUsageEntryPreferenceController.java
new file mode 100644
index 0000000..be74b3c
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataUsageEntryPreferenceController.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.network.NetworkUtils;
+
+/** Preference controller which shows how much data has been used so far. */
+public class DataUsageEntryPreferenceController extends PreferenceController<Preference> {
+
+    public DataUsageEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (!NetworkUtils.hasMobileNetwork(
+                getContext().getSystemService(ConnectivityManager.class))) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        return AVAILABLE;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(formatUsedData());
+    }
+
+    private CharSequence formatUsedData() {
+        SubscriptionManager subscriptionManager = getContext().getSystemService(
+                SubscriptionManager.class);
+        int defaultSubId = subscriptionManager.getDefaultSubscriptionId();
+        if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return null;
+        }
+        SubscriptionPlan defaultPlan = DataUsageUtils.getPrimaryPlan(subscriptionManager,
+                defaultSubId);
+        if (defaultPlan == null) {
+            return null;
+        }
+
+        return DataUsageUtils.bytesToIecUnits(getContext(), defaultPlan.getDataUsageBytes());
+    }
+
+}
diff --git a/src/com/android/car/settings/datausage/DataUsageFragment.java b/src/com/android/car/settings/datausage/DataUsageFragment.java
new file mode 100644
index 0000000..7eaf80f
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataUsageFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Data usage settings homepage. */
+public class DataUsageFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.data_usage_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataUsagePreferenceController.java b/src/com/android/car/settings/datausage/DataUsagePreferenceController.java
new file mode 100644
index 0000000..9c0652a
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataUsagePreferenceController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller to handle the business logic for AppDataUsage preference on the data usage screen
+ */
+public class DataUsagePreferenceController extends PreferenceController<Preference> {
+
+    private SubscriptionManager mSubscriptionManager;
+
+    public DataUsagePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        int subId = DataUsageUtils.getDefaultSubscriptionId(mSubscriptionManager);
+        AppDataUsageFragment appDataUsageFragment = AppDataUsageFragment.newInstance(subId);
+        getFragmentController().launchFragment(appDataUsageFragment);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataUsageSummaryPreference.java b/src/com/android/car/settings/datausage/DataUsageSummaryPreference.java
new file mode 100644
index 0000000..79f2952
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataUsageSummaryPreference.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.StyleRes;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ProgressBarPreference;
+
+/** Extends {@link ProgressBarPreference} in order to support multiple text fields. */
+public class DataUsageSummaryPreference extends ProgressBarPreference {
+
+    private CharSequence mDataLimitText;
+    private CharSequence mRemainingBillingCycleText;
+    private CharSequence mCarrierInfoText;
+    private Intent mManageSubscriptionIntent;
+    @StyleRes
+    private int mCarrierInfoTextStyle = R.style.DataUsageSummaryCarrierInfoTextAppearance;
+
+    public DataUsageSummaryPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    public DataUsageSummaryPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public DataUsageSummaryPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public DataUsageSummaryPreference(Context context) {
+        super(context);
+        init();
+    }
+
+    private void init() {
+        setLayoutResource(R.layout.data_usage_summary_preference);
+    }
+
+    /** Sets the data limit text. */
+    public void setDataLimitText(CharSequence text) {
+        if (!TextUtils.equals(mDataLimitText, text)) {
+            mDataLimitText = text;
+            notifyChanged();
+        }
+    }
+
+    /** Gets the data limit text. */
+    public CharSequence getDataLimitText() {
+        return mDataLimitText;
+    }
+
+    /** Sets the remaining billing cycle description. */
+    public void setRemainingBillingCycleText(CharSequence text) {
+        if (!TextUtils.equals(mRemainingBillingCycleText, text)) {
+            mRemainingBillingCycleText = text;
+            notifyChanged();
+        }
+    }
+
+    /** Gets the remaining billing cycle description. */
+    public CharSequence getRemainingBillingCycleText() {
+        return mRemainingBillingCycleText;
+    }
+
+    /** Sets the carrier info text. */
+    public void setCarrierInfoText(CharSequence text) {
+        if (!TextUtils.equals(mCarrierInfoText, text)) {
+            mCarrierInfoText = text;
+            notifyChanged();
+        }
+    }
+
+    /** Gets the carrier info text. */
+    public CharSequence getCarrierInfoText() {
+        return mCarrierInfoText;
+    }
+
+    /** Sets the carrier info text style. */
+    public void setCarrierInfoTextStyle(@StyleRes int styleId) {
+        if (mCarrierInfoTextStyle != styleId) {
+            mCarrierInfoTextStyle = styleId;
+            notifyChanged();
+        }
+    }
+
+    /** Gets the carrier info text style. */
+    @StyleRes
+    public int getCarrierInfoTextStyle() {
+        return mCarrierInfoTextStyle;
+    }
+
+    /** Sets the manage subscription intent. */
+    public void setManageSubscriptionIntent(Intent intent) {
+        mManageSubscriptionIntent = intent;
+        notifyChanged();
+    }
+
+    /** Gets the manage subscription intent. */
+    public Intent getManageSubscriptionIntent() {
+        return mManageSubscriptionIntent;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+
+        setTextAndVisibility((TextView) view.findViewById(R.id.data_limit_text), mDataLimitText);
+        setTextAndVisibility((TextView) view.findViewById(R.id.remaining_billing_cycle_time_text),
+                mRemainingBillingCycleText);
+        TextView carrierInfo = (TextView) view.findViewById(R.id.carrier_info_text);
+        setTextAndVisibility(carrierInfo, mCarrierInfoText);
+        carrierInfo.setTextAppearance(mCarrierInfoTextStyle);
+
+        Button button = (Button) view.findViewById(R.id.manage_subscription_button);
+        if (mManageSubscriptionIntent != null) {
+            button.setVisibility(View.VISIBLE);
+            button.setOnClickListener(v -> getContext().startActivity(mManageSubscriptionIntent));
+        } else {
+            button.setVisibility(View.GONE);
+        }
+    }
+
+    private void setTextAndVisibility(TextView textView, CharSequence value) {
+        if (!TextUtils.isEmpty(value)) {
+            textView.setText(value);
+            textView.setVisibility(View.VISIBLE);
+        } else {
+            textView.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceController.java b/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceController.java
new file mode 100644
index 0000000..aff3089
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceController.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkTemplate;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
+import android.telephony.TelephonyManager;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.format.Formatter;
+import android.text.style.AbsoluteSizeSpan;
+import android.util.RecurrenceRule;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.network.NetworkUtils;
+import com.android.settingslib.net.DataUsageController;
+import com.android.settingslib.utils.StringUtil;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Business logic for setting the {@link DataUsageSummaryPreference} with the current data usage and
+ * the appropriate summary text.
+ */
+public class DataUsageSummaryPreferenceController extends
+        PreferenceController<DataUsageSummaryPreference> {
+
+    private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1);
+    private static final long MILLIS_IN_AN_HOUR = TimeUnit.HOURS.toMillis(1);
+    private static final long MILLIS_IN_A_MINUTE = TimeUnit.MINUTES.toMillis(1);
+    private static final long MILLIS_IN_A_SECOND = TimeUnit.SECONDS.toMillis(1);
+    private static final int MAX_PROGRESS_BAR_VALUE = 1000;
+
+    @VisibleForTesting
+    static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L);
+
+    private final SubscriptionManager mSubscriptionManager;
+    private final TelephonyManager mTelephonyManager;
+    private final DataUsageController mDataUsageController;
+    private final NetworkTemplate mDefaultTemplate;
+
+    /** Name of the carrier, or null if not available */
+    @Nullable
+    private CharSequence mCarrierName;
+    /** The number of registered plans, [0,N] */
+    private int mDataplanCount;
+    /** The time of the last update in milliseconds since the epoch, or -1 if unknown */
+    private long mSnapshotTime;
+    /** The size of the first registered plan if one exists. -1 if no information is available. */
+    private long mDataplanSize = -1;
+    /**
+     * Limit to track. Size of the first registered plan if one exists. Otherwise size of data limit
+     * or warning.
+     */
+    private long mDataplanTrackingThreshold;
+    /** The number of bytes used since the start of the cycle. */
+    private long mDataplanUse;
+    /** The ending time of the billing cycle in ms since the epoch */
+    private long mCycleEnd;
+    private Intent mManageSubscriptionIntent;
+
+    public DataUsageSummaryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+        mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        mDataUsageController = new DataUsageController(context);
+
+        int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(mSubscriptionManager);
+        mDefaultTemplate = DataUsageUtils.getMobileNetworkTemplate(mTelephonyManager, defaultSubId);
+    }
+
+    @Override
+    protected Class<DataUsageSummaryPreference> getPreferenceType() {
+        return DataUsageSummaryPreference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return NetworkUtils.hasSim(mTelephonyManager) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        getPreference().setMin(0);
+        getPreference().setMax(MAX_PROGRESS_BAR_VALUE);
+    }
+
+    @Override
+    protected void updateState(DataUsageSummaryPreference preference) {
+        DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo(
+                mDefaultTemplate);
+
+        if (mSubscriptionManager != null) {
+            refreshDataplanInfo(info);
+        }
+
+        preference.setTitle(getUsageText());
+        preference.setManageSubscriptionIntent(mManageSubscriptionIntent);
+
+        preference.setDataLimitText(getLimitText(info));
+        preference.setRemainingBillingCycleText(getRemainingBillingCycleTimeText());
+
+        // Carrier Info has special styling based on when it was last updated.
+        preference.setCarrierInfoText(getCarrierInfoText());
+        long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime);
+        if (updateAgeMillis <= WARNING_AGE) {
+            preference.setCarrierInfoTextStyle(R.style.DataUsageSummaryCarrierInfoTextAppearance);
+        } else {
+            preference.setCarrierInfoTextStyle(
+                    R.style.DataUsageSummaryCarrierInfoWarningTextAppearance);
+        }
+
+        // Set the progress bar values.
+        preference.setMinLabel(DataUsageUtils.bytesToIecUnits(getContext(), /* byteValue= */ 0));
+        preference.setMaxLabel(
+                DataUsageUtils.bytesToIecUnits(getContext(), mDataplanTrackingThreshold));
+        preference.setProgress(scaleUsage(mDataplanUse, mDataplanTrackingThreshold));
+    }
+
+    private CharSequence getUsageText() {
+        Formatter.BytesResult usedResult = Formatter.formatBytes(getContext().getResources(),
+                mDataplanUse, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS);
+        SpannableString usageNumberText = new SpannableString(usedResult.value);
+        int textSize = getContext().getResources().getDimensionPixelSize(
+                R.dimen.usage_number_text_size);
+
+        // Set the usage text (only the number) to the size defined by usage_number_text_size.
+        usageNumberText.setSpan(new AbsoluteSizeSpan(textSize), /* start= */ 0,
+                usageNumberText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        CharSequence template = getContext().getText(R.string.data_used_formatted);
+        CharSequence usageText = TextUtils.expandTemplate(template, usageNumberText,
+                usedResult.units);
+        return usageText;
+    }
+
+
+    private CharSequence getLimitText(DataUsageController.DataUsageInfo info) {
+        if (info.warningLevel > 0 && info.limitLevel > 0) {
+            return TextUtils.expandTemplate(
+                    getContext().getText(R.string.cell_data_warning_and_limit),
+                    DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel),
+                    DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel));
+        } else if (info.warningLevel > 0) {
+            return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_warning),
+                    DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel));
+        } else if (info.limitLevel > 0) {
+            return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_limit),
+                    DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel));
+        }
+
+        return null;
+    }
+
+    private CharSequence getRemainingBillingCycleTimeText() {
+        long millisLeft = mCycleEnd - System.currentTimeMillis();
+        if (millisLeft <= 0) {
+            return getContext().getString(R.string.billing_cycle_none_left);
+        } else {
+            int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY);
+            return daysLeft < 1
+                    ? getContext().getString(R.string.billing_cycle_less_than_one_day_left)
+                    : getContext().getResources().getQuantityString(
+                            R.plurals.billing_cycle_days_left, daysLeft, daysLeft);
+        }
+    }
+
+    private CharSequence getCarrierInfoText() {
+        if (mDataplanCount > 0 && mSnapshotTime >= 0L) {
+            long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime);
+
+            int textResourceId;
+            CharSequence updateTime = null;
+            if (updateAgeMillis == 0) {
+                if (mCarrierName != null) {
+                    textResourceId = R.string.carrier_and_update_now_text;
+                } else {
+                    textResourceId = R.string.no_carrier_update_now_text;
+                }
+            } else {
+                if (mCarrierName != null) {
+                    textResourceId = R.string.carrier_and_update_text;
+                } else {
+                    textResourceId = R.string.no_carrier_update_text;
+                }
+                updateTime = StringUtil.formatElapsedTime(getContext(),
+                        updateAgeMillis, /* withSeconds= */ false);
+            }
+            return TextUtils.expandTemplate(getContext().getText(textResourceId), mCarrierName,
+                    updateTime);
+        }
+
+        return null;
+    }
+
+    private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) {
+        // Reset data before overwriting.
+        mCarrierName = null;
+        mDataplanCount = 0;
+        mSnapshotTime = -1L;
+        mDataplanSize = -1L;
+        mDataplanTrackingThreshold = getSummaryLimit(info);
+        mDataplanUse = info.usageLevel;
+        mCycleEnd = info.cycleEnd;
+
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        SubscriptionInfo subInfo = mSubscriptionManager.getDefaultDataSubscriptionInfo();
+        if (subInfo != null) {
+            mCarrierName = subInfo.getCarrierName();
+            List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(defaultSubId);
+            SubscriptionPlan primaryPlan = DataUsageUtils.getPrimaryPlan(mSubscriptionManager,
+                    defaultSubId);
+            if (primaryPlan != null) {
+                mDataplanCount = plans.size();
+                mDataplanSize = primaryPlan.getDataLimitBytes();
+                if (mDataplanSize == SubscriptionPlan.BYTES_UNLIMITED) {
+                    mDataplanSize = -1L;
+                }
+                mDataplanTrackingThreshold = mDataplanSize;
+                mDataplanUse = primaryPlan.getDataUsageBytes();
+
+                RecurrenceRule rule = primaryPlan.getCycleRule();
+                if (rule != null && rule.start != null && rule.end != null) {
+                    mCycleEnd = rule.end.toEpochSecond() * MILLIS_IN_A_SECOND;
+                }
+                mSnapshotTime = primaryPlan.getDataUsageTime();
+            }
+        }
+        mManageSubscriptionIntent = mSubscriptionManager.createManageSubscriptionIntent(
+                defaultSubId);
+    }
+
+    /** Scales the current usage to be an integer between 0 and {@link #MAX_PROGRESS_BAR_VALUE}. */
+    private int scaleUsage(long usage, long maxUsage) {
+        if (maxUsage == 0) {
+            return 0;
+        }
+        return (int) ((usage / (float) maxUsage) * MAX_PROGRESS_BAR_VALUE);
+    }
+
+    /**
+     * Gets the max displayed limit based on {@link DataUsageController.DataUsageInfo}.
+     *
+     * @return the most appropriate limit for the data usage summary. Use the total usage when it
+     * is higher than the limit and warning level. Use the limit when it is set and less than usage.
+     * Otherwise use warning level.
+     */
+    private static long getSummaryLimit(DataUsageController.DataUsageInfo info) {
+        long limit = info.limitLevel;
+        if (limit <= 0) {
+            limit = info.warningLevel;
+        }
+        if (info.usageLevel > limit) {
+            limit = info.usageLevel;
+        }
+        return limit;
+    }
+
+    /**
+     * Returns the time since the last carrier update, as defined by {@link #mSnapshotTime},
+     * truncated to the nearest day / hour / minute in milliseconds, or 0 if less than 1 min.
+     */
+    private long calculateTruncatedUpdateAge(long snapshotTime) {
+        long updateAgeMillis = System.currentTimeMillis() - snapshotTime;
+
+        // Round to nearest whole unit
+        if (updateAgeMillis >= MILLIS_IN_A_DAY) {
+            return (updateAgeMillis / MILLIS_IN_A_DAY) * MILLIS_IN_A_DAY;
+        } else if (updateAgeMillis >= MILLIS_IN_AN_HOUR) {
+            return (updateAgeMillis / MILLIS_IN_AN_HOUR) * MILLIS_IN_AN_HOUR;
+        } else if (updateAgeMillis >= MILLIS_IN_A_MINUTE) {
+            return (updateAgeMillis / MILLIS_IN_A_MINUTE) * MILLIS_IN_A_MINUTE;
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataUsageUtils.java b/src/com/android/car/settings/datausage/DataUsageUtils.java
new file mode 100644
index 0000000..ea85ccb
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataUsageUtils.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.content.Context;
+import android.net.NetworkTemplate;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
+import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
+import android.text.format.Formatter;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.util.CollectionUtils;
+
+import java.util.List;
+
+/** Provides helpful utilities related to data usage. */
+public final class DataUsageUtils {
+
+    @VisibleForTesting
+    static final long PETA = 1000000000000000L;
+
+    private DataUsageUtils() {
+    }
+
+    /**
+     * Returns the mobile network template given the subscription id.
+     */
+    public static NetworkTemplate getMobileNetworkTemplate(TelephonyManager telephonyManager,
+            int subscriptionId) {
+        NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
+                telephonyManager.getSubscriberId(subscriptionId));
+        return NetworkTemplate.normalize(mobileAll, telephonyManager.getMergedSubscriberIds());
+    }
+
+    /**
+     * Returns the default subscription if available else returns
+     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+     */
+    public static int getDefaultSubscriptionId(SubscriptionManager subscriptionManager) {
+        if (subscriptionManager == null) {
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
+        SubscriptionInfo subscriptionInfo = subscriptionManager.getDefaultDataSubscriptionInfo();
+        if (subscriptionInfo == null) {
+            List<SubscriptionInfo> list = subscriptionManager.getAllSubscriptionInfoList();
+            if (list.size() == 0) {
+                return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+            }
+            subscriptionInfo = list.get(0);
+        }
+        return subscriptionInfo.getSubscriptionId();
+    }
+
+    /**
+     * Format byte value to readable string using IEC units.
+     */
+    public static CharSequence bytesToIecUnits(Context context, long byteValue) {
+        Formatter.BytesResult res = Formatter.formatBytes(context.getResources(), byteValue,
+                Formatter.FLAG_IEC_UNITS);
+        return BidiFormatter.getInstance().unicodeWrap(context.getString(
+                com.android.internal.R.string.fileSizeSuffix, res.value, res.units));
+    }
+
+    /**
+     * Returns the primary subscription plan. Returns {@code null} if {@link SubscriptionPlan}
+     * doesn't exist for a given subscriptionId or if the first {@link SubscriptionPlan} has
+     * invalid properties.
+     */
+    @Nullable
+    public static SubscriptionPlan getPrimaryPlan(SubscriptionManager subManager,
+            int subscriptionId) {
+        List<SubscriptionPlan> plans = subManager.getSubscriptionPlans(subscriptionId);
+        if (CollectionUtils.isEmpty(plans)) {
+            return null;
+        }
+        // First plan in the list is the primary plan
+        SubscriptionPlan plan = plans.get(0);
+        return plan.getDataLimitBytes() > 0
+                && saneSize(plan.getDataUsageBytes())
+                && plan.getCycleRule() != null ? plan : null;
+    }
+
+    private static boolean saneSize(long value) {
+        return value >= 0L && value < PETA;
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataWarningAndLimitBasePreferenceController.java b/src/com/android/car/settings/datausage/DataWarningAndLimitBasePreferenceController.java
new file mode 100644
index 0000000..b8315b8
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataWarningAndLimitBasePreferenceController.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.NetworkTemplate;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.NetworkPolicyEditor;
+
+/**
+ * Defines the shared getters and setters used by {@link PreferenceController} implementations
+ * related to data usage warning and limit.
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller
+ *            expects to operate.
+ */
+public abstract class DataWarningAndLimitBasePreferenceController<V extends Preference> extends
+        PreferenceController<V> {
+
+    private NetworkPolicyEditor mNetworkPolicyEditor;
+    private NetworkTemplate mNetworkTemplate;
+
+    public DataWarningAndLimitBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /** Gets the {@link NetworkPolicyEditor}. */
+    public NetworkPolicyEditor getNetworkPolicyEditor() {
+        return mNetworkPolicyEditor;
+    }
+
+    /** Sets the {@link NetworkPolicyEditor}. */
+    public void setNetworkPolicyEditor(NetworkPolicyEditor networkPolicyEditor) {
+        mNetworkPolicyEditor = networkPolicyEditor;
+    }
+
+    /** Gets the {@link NetworkTemplate}. */
+    public NetworkTemplate getNetworkTemplate() {
+        return mNetworkTemplate;
+    }
+
+    /** Sets the {@link NetworkTemplate}. */
+    public void setNetworkTemplate(NetworkTemplate networkTemplate) {
+        mNetworkTemplate = networkTemplate;
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataWarningAndLimitFragment.java b/src/com/android/car/settings/datausage/DataWarningAndLimitFragment.java
new file mode 100644
index 0000000..8aa3fc2
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataWarningAndLimitFragment.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.content.Context;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkTemplate;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.NetworkPolicyEditor;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Screen to set data warning and limit thresholds. */
+public class DataWarningAndLimitFragment extends SettingsFragment {
+
+    private TelephonyManager mTelephonyManager;
+    private SubscriptionManager mSubscriptionManager;
+    private NetworkPolicyEditor mPolicyEditor;
+    private NetworkTemplate mNetworkTemplate;
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.data_warning_and_limit_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        mPolicyEditor = new NetworkPolicyEditor(NetworkPolicyManager.from(context));
+        mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+        mNetworkTemplate = DataUsageUtils.getMobileNetworkTemplate(mTelephonyManager,
+                DataUsageUtils.getDefaultSubscriptionId(mSubscriptionManager));
+
+        // Loads the current policies to the policy editor cache.
+        mPolicyEditor.read();
+
+        List<DataWarningAndLimitBasePreferenceController> preferenceControllers =
+                Arrays.asList(
+                        use(CycleResetDayOfMonthPickerPreferenceController.class,
+                                R.string.pk_data_usage_cycle),
+                        use(DataWarningPreferenceController.class, R.string.pk_data_warning_group),
+                        use(DataLimitPreferenceController.class, R.string.pk_data_limit_group));
+
+        for (DataWarningAndLimitBasePreferenceController preferenceController :
+                preferenceControllers) {
+            preferenceController.setNetworkPolicyEditor(mPolicyEditor);
+            preferenceController.setNetworkTemplate(mNetworkTemplate);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/datausage/DataWarningPreferenceController.java b/src/com/android/car/settings/datausage/DataWarningPreferenceController.java
new file mode 100644
index 0000000..b16857a
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataWarningPreferenceController.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static android.net.NetworkPolicy.WARNING_DISABLED;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.datausage.UsageBytesThresholdPickerDialog.BytesThresholdPickedListener;
+import com.android.settingslib.net.DataUsageController;
+
+/** Controls setting the data warning threshold. */
+public class DataWarningPreferenceController extends
+        DataWarningAndLimitBasePreferenceController<PreferenceGroup> implements
+        Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
+
+    private final DataUsageController mDataUsageController;
+    private final BytesThresholdPickedListener mThresholdPickedListener = numBytes -> {
+        getNetworkPolicyEditor().setPolicyWarningBytes(getNetworkTemplate(), numBytes);
+        refreshUi();
+    };
+
+    private TwoStatePreference mEnableDataWarningPreference;
+    private Preference mSetDataWarningPreference;
+
+    public DataWarningPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mDataUsageController = new DataUsageController(getContext());
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mEnableDataWarningPreference = (TwoStatePreference) getPreference().findPreference(
+                getContext().getString(R.string.pk_data_set_warning));
+        mEnableDataWarningPreference.setOnPreferenceChangeListener(this);
+        mSetDataWarningPreference = getPreference().findPreference(
+                getContext().getString(R.string.pk_data_warning));
+        mSetDataWarningPreference.setOnPreferenceClickListener(this);
+
+        UsageBytesThresholdPickerDialog dialog =
+                (UsageBytesThresholdPickerDialog) getFragmentController().findDialogByTag(
+                        UsageBytesThresholdPickerDialog.TAG);
+        if (dialog != null) {
+            dialog.setBytesThresholdPickedListener(mThresholdPickedListener);
+        }
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        long warningBytes = getNetworkPolicyEditor().getPolicyWarningBytes(getNetworkTemplate());
+        if (warningBytes == WARNING_DISABLED) {
+            mSetDataWarningPreference.setSummary(null);
+            mEnableDataWarningPreference.setChecked(false);
+        } else {
+            mSetDataWarningPreference.setSummary(
+                    DataUsageUtils.bytesToIecUnits(getContext(), warningBytes));
+            mEnableDataWarningPreference.setChecked(true);
+        }
+
+        mSetDataWarningPreference.setEnabled(mEnableDataWarningPreference.isChecked());
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        boolean enabled = (Boolean) newValue;
+        getNetworkPolicyEditor().setPolicyWarningBytes(getNetworkTemplate(),
+                enabled ? mDataUsageController.getDefaultWarningLevel() : WARNING_DISABLED);
+        refreshUi();
+        return true;
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        UsageBytesThresholdPickerDialog dialog = UsageBytesThresholdPickerDialog.newInstance(
+                R.string.data_usage_warning_editor_title,
+                getNetworkPolicyEditor().getPolicyWarningBytes(getNetworkTemplate()));
+        dialog.setBytesThresholdPickedListener(mThresholdPickedListener);
+        getFragmentController().showDialog(dialog, UsageBytesThresholdPickerDialog.TAG);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/datausage/SummaryForAllUidLoader.java b/src/com/android/car/settings/datausage/SummaryForAllUidLoader.java
new file mode 100644
index 0000000..f65a978
--- /dev/null
+++ b/src/com/android/car/settings/datausage/SummaryForAllUidLoader.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.content.Context;
+import android.net.INetworkStatsSession;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.loader.content.AsyncTaskLoader;
+
+/**
+ * Fetches the network stats using the {@link INetworkStatsSession}.
+ *
+ * <p>Class is taken from {@link com.android.settingslib.net.SummaryForAllUidLoader}. The only
+ * difference is we are using {@link AsyncTaskLoader} instead of {@link
+ * android.content.AsyncTaskLoader}.
+ */
+public class SummaryForAllUidLoader extends AsyncTaskLoader<NetworkStats> {
+    private static final String KEY_TEMPLATE = "template";
+    private static final String KEY_START = "start";
+    private static final String KEY_END = "end";
+
+    private final INetworkStatsSession mSession;
+    private final Bundle mArgs;
+
+    /**
+     * Builds the bundle given the template, start and end.
+     */
+    public static Bundle buildArgs(NetworkTemplate template, long start, long end) {
+        Bundle args = new Bundle();
+        args.putParcelable(KEY_TEMPLATE, template);
+        args.putLong(KEY_START, start);
+        args.putLong(KEY_END, end);
+        return args;
+    }
+
+    public SummaryForAllUidLoader(Context context, INetworkStatsSession session, Bundle args) {
+        super(context);
+        mSession = session;
+        mArgs = args;
+    }
+
+    @Override
+    protected void onStartLoading() {
+        super.onStartLoading();
+        forceLoad();
+    }
+
+    @Override
+    public NetworkStats loadInBackground() {
+        NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
+        long start = mArgs.getLong(KEY_START);
+        long end = mArgs.getLong(KEY_END);
+
+        try {
+            return mSession.getSummaryForAllUid(template, start, end, /* includeTags= */ false);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    @Override
+    protected void onStopLoading() {
+        super.onStopLoading();
+        cancelLoad();
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+        cancelLoad();
+    }
+}
diff --git a/src/com/android/car/settings/datausage/UsageBytesThresholdPickerDialog.java b/src/com/android/car/settings/datausage/UsageBytesThresholdPickerDialog.java
new file mode 100644
index 0000000..02169d4
--- /dev/null
+++ b/src/com/android/car/settings/datausage/UsageBytesThresholdPickerDialog.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.NumberPicker;
+
+import androidx.annotation.IntegerRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.DialogFragment;
+
+import com.android.car.settings.R;
+
+/** Dialog that is used to pick the data usage warning/limit threshold bytes. */
+public class UsageBytesThresholdPickerDialog extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    /** Tag used to identify dialog in {@link androidx.fragment.app.FragmentManager}. */
+    public static final String TAG = "UsageBytesThresholdPickerDialog";
+    private static final String ARG_DIALOG_TITLE_RES = "arg_dialog_title_res";
+    private static final String ARG_CURRENT_THRESHOLD = "arg_current_threshold";
+    private static final float MB_GB_SUFFIX_THRESHOLD = 1.5f;
+
+    @VisibleForTesting
+    static final long MIB_IN_BYTES = 1024 * 1024;
+    @VisibleForTesting
+    static final long GIB_IN_BYTES = MIB_IN_BYTES * 1024;
+    @VisibleForTesting
+    static final long MAX_DATA_LIMIT_BYTES = 50000 * GIB_IN_BYTES;
+
+    // Number pickers can be used to pick strings as well.
+    private NumberPicker mBytesUnits;
+    private View mUpArrow;
+    private View mDownArrow;
+    private EditText mThresholdEditor;
+    private BytesThresholdPickedListener mBytesThresholdPickedListener;
+    private long mCurrentThreshold;
+
+    /**
+     * Creates a new instance of the {@link UsageBytesThresholdPickerDialog} with the
+     * {@code currentThreshold} represented with the best units available.
+     */
+    public static UsageBytesThresholdPickerDialog newInstance(@IntegerRes int dialogTitle,
+            long currentThreshold) {
+        UsageBytesThresholdPickerDialog dialog = new UsageBytesThresholdPickerDialog();
+        Bundle args = new Bundle();
+        args.putInt(ARG_DIALOG_TITLE_RES, dialogTitle);
+        args.putLong(ARG_CURRENT_THRESHOLD, currentThreshold);
+        dialog.setArguments(args);
+        return dialog;
+    }
+
+    /** Sets a {@link BytesThresholdPickedListener}. */
+    public void setBytesThresholdPickedListener(
+            BytesThresholdPickedListener bytesThresholdPickedListener) {
+        mBytesThresholdPickedListener = bytesThresholdPickedListener;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+
+        // Use builder context to keep consistent theme.
+        LayoutInflater inflater = LayoutInflater.from(builder.getContext());
+        View view = inflater.inflate(R.layout.usage_bytes_threshold_picker,
+                /* root= */ null, /* attachToRoot= */ false);
+
+        mCurrentThreshold = getArguments().getLong(ARG_CURRENT_THRESHOLD);
+        if (mCurrentThreshold < 0) {
+            mCurrentThreshold = 0;
+        }
+
+        String[] units = getContext().getResources().getStringArray(R.array.bytes_picker_sizes);
+        mBytesUnits = view.findViewById(R.id.bytes_units);
+        mBytesUnits.setMinValue(0);
+        mBytesUnits.setMaxValue(units.length - 1);
+        mBytesUnits.setDisplayedValues(units);
+
+        mThresholdEditor = view.findViewById(R.id.bytes_threshold);
+
+        mUpArrow = view.findViewById(R.id.up_arrow_container);
+        mUpArrow.setOnClickListener(v -> mBytesUnits.setValue(mBytesUnits.getValue() - 1));
+
+        mDownArrow = view.findViewById(R.id.down_arrow_container);
+        mDownArrow.setOnClickListener(v -> mBytesUnits.setValue(mBytesUnits.getValue() + 1));
+
+        updateCurrentView(mCurrentThreshold);
+
+        return builder
+                .setTitle(getArguments().getInt(ARG_DIALOG_TITLE_RES))
+                .setView(view)
+                .setPositiveButton(R.string.usage_bytes_threshold_picker_positive_button,
+                        /* onClickListener= */ this)
+                .create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            long newThreshold = getCurrentThreshold();
+            if (mBytesThresholdPickedListener != null
+                    && mCurrentThreshold != newThreshold) {
+                mBytesThresholdPickedListener.onThresholdPicked(newThreshold);
+            }
+        }
+    }
+
+    /** Gets the threshold currently represented by this {@link UsageBytesThresholdPickerDialog}. */
+    public long getCurrentThreshold() {
+        String bytesString = mThresholdEditor.getText().toString();
+        if (bytesString.isEmpty() || bytesString.equals(".")) {
+            bytesString = "0";
+        }
+
+        long bytes = (long) (Float.valueOf(bytesString) * (mBytesUnits.getValue() == 0
+                ? MIB_IN_BYTES : GIB_IN_BYTES));
+
+        // To fix the overflow problem.
+        long correctedBytes = Math.min(MAX_DATA_LIMIT_BYTES, bytes);
+
+        return correctedBytes;
+    }
+
+    @VisibleForTesting
+    void setThresholdEditor(long threshold) {
+        updateCurrentView(threshold);
+    }
+
+    private void updateCurrentView(long threshold) {
+        String bytesText;
+        if (threshold > MB_GB_SUFFIX_THRESHOLD * GIB_IN_BYTES) {
+            bytesText = formatText(threshold / (float) GIB_IN_BYTES);
+            mBytesUnits.setValue(1);
+        } else {
+            bytesText = formatText(threshold / (float) MIB_IN_BYTES);
+            mBytesUnits.setValue(0);
+        }
+        mThresholdEditor.setText(bytesText);
+        mThresholdEditor.setSelection(0, bytesText.length());
+    }
+
+    private String formatText(float v) {
+        v = Math.round(v * 100) / 100f;
+        return String.valueOf(v);
+    }
+
+    /** A listener that is called when a date is selected. */
+    public interface BytesThresholdPickedListener {
+        /** A method that determines how to process the selected day of month. */
+        void onThresholdPicked(long numBytes);
+    }
+}
diff --git a/src/com/android/car/settings/datausage/UsageCycleResetDayOfMonthPickerDialog.java b/src/com/android/car/settings/datausage/UsageCycleResetDayOfMonthPickerDialog.java
new file mode 100644
index 0000000..17b5ece
--- /dev/null
+++ b/src/com/android/car/settings/datausage/UsageCycleResetDayOfMonthPickerDialog.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.NumberPicker;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.DialogFragment;
+
+import com.android.car.settings.R;
+
+/** Dialog that is used to pick the start day of month to track a data usage cycle. */
+public class UsageCycleResetDayOfMonthPickerDialog extends DialogFragment {
+
+    private static final String ARG_SELECTED_DAY_OF_MONTH = "arg_selected_day_of_month";
+
+    /**
+     * Defines the time frequency at which touch listener should be triggered when holding either
+     * arrow button.
+     */
+    @VisibleForTesting
+    static final int TIME_INTERVAL_MILLIS = 250;
+
+    private static final int MIN_DAY = 1;
+    private static final int MAX_DAY = 31;
+    private ResetDayOfMonthPickedListener mResetDayOfMonthPickedListener;
+    private NumberPicker mCycleDayOfMonthPicker;
+    private View mUpArrow;
+    private View mDownArrow;
+
+    /**
+     * Creates a new instance of the {@link UsageCycleResetDayOfMonthPickerDialog} with the {@link
+     * NumberPicker} set to showing the value {@code startDayOfMonth}.
+     */
+    public static UsageCycleResetDayOfMonthPickerDialog newInstance(int startDayOfMonth) {
+        UsageCycleResetDayOfMonthPickerDialog dialog = new UsageCycleResetDayOfMonthPickerDialog();
+        Bundle args = new Bundle();
+        args.putInt(ARG_SELECTED_DAY_OF_MONTH, startDayOfMonth);
+        dialog.setArguments(args);
+        return dialog;
+    }
+
+    /** Sets a {@link ResetDayOfMonthPickedListener}. */
+    public void setResetDayOfMonthPickedListener(
+            ResetDayOfMonthPickedListener resetDayOfMonthPickedListener) {
+        mResetDayOfMonthPickedListener = resetDayOfMonthPickedListener;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+
+        // Use builder context to keep consistent theme.
+        LayoutInflater inflater = LayoutInflater.from(builder.getContext());
+        View view = inflater.inflate(R.layout.usage_cycle_reset_day_of_month_picker,
+                /* root= */ null, /* attachToRoot= */ false);
+
+        int cycleDayOfMonth = getArguments().getInt(ARG_SELECTED_DAY_OF_MONTH);
+        if (cycleDayOfMonth < MIN_DAY) {
+            cycleDayOfMonth = MIN_DAY;
+        }
+        if (cycleDayOfMonth > MAX_DAY) {
+            cycleDayOfMonth = MAX_DAY;
+        }
+
+        mCycleDayOfMonthPicker = view.findViewById(R.id.cycle_reset_day_of_month);
+        mCycleDayOfMonthPicker.setMinValue(MIN_DAY);
+        mCycleDayOfMonthPicker.setMaxValue(MAX_DAY);
+        mCycleDayOfMonthPicker.setValue(cycleDayOfMonth);
+        mCycleDayOfMonthPicker.setWrapSelectorWheel(true);
+
+        mUpArrow = view.findViewById(R.id.up_arrow_container);
+        mUpArrow.setOnTouchListener(new CycleArrowTouchListener(
+                () -> mCycleDayOfMonthPicker.setValue(mCycleDayOfMonthPicker.getValue() - 1),
+                TIME_INTERVAL_MILLIS));
+
+        mDownArrow = view.findViewById(R.id.down_arrow_container);
+        mDownArrow.setOnTouchListener(new CycleArrowTouchListener(
+                () -> mCycleDayOfMonthPicker.setValue(mCycleDayOfMonthPicker.getValue() + 1),
+                TIME_INTERVAL_MILLIS));
+
+        return builder
+                .setTitle(R.string.cycle_reset_day_of_month_picker_title)
+                .setView(view)
+                .setPositiveButton(R.string.cycle_reset_day_of_month_picker_positive_button,
+                        (dialog, which) -> {
+                            if (which == DialogInterface.BUTTON_POSITIVE) {
+                                if (mResetDayOfMonthPickedListener != null) {
+                                    mResetDayOfMonthPickedListener.onDayOfMonthPicked(
+                                            mCycleDayOfMonthPicker.getValue());
+                                }
+                            }
+                        })
+                .create();
+    }
+
+    /** Gets the current day of month selected by the {@link NumberPicker}. */
+    public int getSelectedDayOfMonth() {
+        return mCycleDayOfMonthPicker.getValue();
+    }
+
+    /** A listener that is called when a date is selected. */
+    public interface ResetDayOfMonthPickedListener {
+        /** A method that determines how to process the selected day of month. */
+        void onDayOfMonthPicked(int dayOfMonth);
+    }
+
+    private static class CycleArrowTouchListener implements View.OnTouchListener {
+
+        private final IntervalActionListener mIntervalActionListener;
+        private final long mTimeIntervalMillis;
+
+        private Handler mHandler = new Handler();
+        private Runnable mAction;
+
+        CycleArrowTouchListener(IntervalActionListener listener, long timeIntervalMillis) {
+            mIntervalActionListener = listener;
+            mTimeIntervalMillis = timeIntervalMillis;
+
+            mAction = () -> {
+                mHandler.postDelayed(this.mAction, mTimeIntervalMillis);
+                maybeTriggerAction();
+            };
+        }
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    mHandler.removeCallbacks(mAction);
+                    mHandler.postDelayed(mAction, mTimeIntervalMillis);
+                    maybeTriggerAction();
+                    v.setPressed(true);
+                    return true;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mHandler.removeCallbacks(mAction);
+                    v.setPressed(false);
+                    return true;
+            }
+            return false;
+        }
+
+        private void maybeTriggerAction() {
+            if (mIntervalActionListener != null) {
+                mIntervalActionListener.takeAction();
+            }
+        }
+
+        /** Action that should be taken per time interval that the button is held. */
+        interface IntervalActionListener {
+            /** Defines the action to take at each time interval. */
+            void takeAction();
+        }
+    }
+}
diff --git a/src/com/android/car/settings/datetime/AutoDatetimeTogglePreferenceController.java b/src/com/android/car/settings/datetime/AutoDatetimeTogglePreferenceController.java
new file mode 100644
index 0000000..f311e6f
--- /dev/null
+++ b/src/com/android/car/settings/datetime/AutoDatetimeTogglePreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Business logic which controls the auto datetime toggle.
+ */
+public class AutoDatetimeTogglePreferenceController extends
+        PreferenceController<TwoStatePreference> {
+
+    public AutoDatetimeTogglePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(isEnabled());
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean isAutoDatetimeEnabled = (boolean) newValue;
+        Settings.Global.putInt(getContext().getContentResolver(), Settings.Global.AUTO_TIME,
+                isAutoDatetimeEnabled ? 1 : 0);
+
+        getContext().sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+        return true;
+    }
+
+    private boolean isEnabled() {
+        return Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.AUTO_TIME, 0) > 0;
+    }
+}
diff --git a/src/com/android/car/settings/datetime/AutoTimeZoneTogglePreferenceController.java b/src/com/android/car/settings/datetime/AutoTimeZoneTogglePreferenceController.java
new file mode 100644
index 0000000..2f001cf
--- /dev/null
+++ b/src/com/android/car/settings/datetime/AutoTimeZoneTogglePreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Business logic for the toggle which chooses to use the network provided time zone.
+ */
+public class AutoTimeZoneTogglePreferenceController extends
+        PreferenceController<TwoStatePreference> {
+
+    public AutoTimeZoneTogglePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean isAutoTimezoneEnabled = (boolean) newValue;
+        Settings.Global.putInt(getContext().getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
+                isAutoTimezoneEnabled ? 1 : 0);
+
+        getContext().sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+        return true;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(isEnabled());
+    }
+
+    private boolean isEnabled() {
+        return Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.AUTO_TIME_ZONE, 0) > 0;
+    }
+}
diff --git a/src/com/android/car/settings/datetime/DatePickerFragment.java b/src/com/android/car/settings/datetime/DatePickerFragment.java
index 4a2af00..67714c7 100644
--- a/src/com/android/car/settings/datetime/DatePickerFragment.java
+++ b/src/com/android/car/settings/datetime/DatePickerFragment.java
@@ -22,6 +22,9 @@
 import android.widget.Button;
 import android.widget.DatePicker;
 
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
+
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
 
@@ -35,14 +38,22 @@
 
     private DatePicker mDatePicker;
 
-    public static DatePickerFragment getInstance() {
-        DatePickerFragment datePickerFragment = new DatePickerFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.date_picker_title);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.date_picker);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        datePickerFragment.setArguments(bundle);
-        return datePickerFragment;
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return R.layout.date_picker;
+    }
+
+    @Override
+    @StringRes
+    protected int getTitleId() {
+        return R.string.date_picker_title;
     }
 
     @Override
diff --git a/src/com/android/car/settings/datetime/DatePickerPreferenceController.java b/src/com/android/car/settings/datetime/DatePickerPreferenceController.java
new file mode 100644
index 0000000..a4b193c
--- /dev/null
+++ b/src/com/android/car/settings/datetime/DatePickerPreferenceController.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.Calendar;
+
+/**
+ * Business logic for the preference which allows for picking the date.
+ */
+public class DatePickerPreferenceController extends PreferenceController<Preference> {
+
+    private final IntentFilter mIntentFilter;
+    private final BroadcastReceiver mTimeChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshUi();
+        }
+    };
+
+    public DatePickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        // Listens to all three actions because they can all affect the date shown on the
+        // screen.
+        mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(Intent.ACTION_TIME_CHANGED);
+        mIntentFilter.addAction(Intent.ACTION_TIME_TICK);
+        mIntentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /** Starts the broadcast receiver which listens for time changes */
+    @Override
+    protected void onStartInternal() {
+        getContext().registerReceiver(mTimeChangeReceiver, mIntentFilter);
+    }
+
+    /** Stops the broadcast receiver which listens for time changes */
+    @Override
+    protected void onStopInternal() {
+        getContext().unregisterReceiver(mTimeChangeReceiver);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        preference.setSummary(DateFormat.getLongDateFormat(getContext()).format(
+                Calendar.getInstance().getTime()));
+        preference.setEnabled(!autoDatetimeIsEnabled());
+    }
+
+    private boolean autoDatetimeIsEnabled() {
+        return Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.AUTO_TIME, 0) > 0;
+    }
+}
+
diff --git a/src/com/android/car/settings/datetime/DateTimeToggleLineItem.java b/src/com/android/car/settings/datetime/DateTimeToggleLineItem.java
deleted file mode 100644
index 3ade8f5..0000000
--- a/src/com/android/car/settings/datetime/DateTimeToggleLineItem.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.datetime;
-
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-
-import androidx.car.widget.TextListItem;
-
-/**
- * A LineItem that displays and sets system auto date/time.
- */
-class DateTimeToggleLineItem extends TextListItem
-        implements DatetimeSettingsFragment.ListRefreshObserver {
-    private Context mContext;
-    private final String mSettingString;
-
-    public DateTimeToggleLineItem(Context context, String title, String desc,
-            String settingString) {
-        super(context);
-        mContext = context;
-        mSettingString = settingString;
-        setTitle(title);
-        setBody(desc);
-        setSwitch(
-                isChecked(),
-                /* showDivider= */ false,
-                (button, isChecked) -> {
-                    Settings.Global.putInt(
-                            mContext.getContentResolver(),
-                            mSettingString,
-                            isChecked ? 1 : 0);
-                    mContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
-                });
-    }
-
-    @Override
-    public void onPreRefresh() {
-        setSwitchState(isChecked());
-    }
-
-    private boolean isChecked() {
-        return Settings.Global.getInt(mContext.getContentResolver(), mSettingString, 0) > 0;
-    }
-}
diff --git a/src/com/android/car/settings/datetime/DatetimeSettingsFragment.java b/src/com/android/car/settings/datetime/DatetimeSettingsFragment.java
index 3d5cb97..265c127 100644
--- a/src/com/android/car/settings/datetime/DatetimeSettingsFragment.java
+++ b/src/com/android/car/settings/datetime/DatetimeSettingsFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -13,106 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
+
 package com.android.car.settings.datetime;
 
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.provider.Settings;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.ListItemProvider.ListProvider;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.android.car.settings.common.SettingsFragment;
 
 /**
  * Configures date and time.
  */
-public class DatetimeSettingsFragment extends ListItemSettingsFragment {
-    private static final IntentFilter TIME_CHANGED_FILTER =
-            new IntentFilter(Intent.ACTION_TIME_CHANGED);
-
+public class DatetimeSettingsFragment extends SettingsFragment {
     // Minimum time is Nov 5, 2007, 0:00.
     public static final long MIN_DATE = 1194220800000L;
 
-    private List<ListItem> mListItems;
-
-    private final TimeChangedBroadCastReceiver mTimeChangedBroadCastReceiver =
-            new TimeChangedBroadCastReceiver();
-
-    /**
-     * Observes list refreshes.
-     */
-    public interface ListRefreshObserver {
-
-        /**
-         * Gets called when the list is about to refresh. Subclass should set the view to ListItem
-         * state, so can be reflected on next fresh.
-         */
-        void onPreRefresh();
-    }
-
-    public static DatetimeSettingsFragment getInstance() {
-        DatetimeSettingsFragment datetimeSettingsFragment = new DatetimeSettingsFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.date_and_time_settings_title);
-        datetimeSettingsFragment.setArguments(bundle);
-        return datetimeSettingsFragment;
-    }
-
     @Override
-    public ListItemProvider getItemProvider() {
-        return new ListProvider(initializeListItems());
-    }
-
-    private List<ListItem> initializeListItems() {
-        mListItems = new ArrayList<>();
-        mListItems.add(new DateTimeToggleLineItem(getContext(),
-                getString(R.string.date_time_auto),
-                getString(R.string.date_time_auto_summary),
-                Settings.Global.AUTO_TIME));
-        mListItems.add(new DateTimeToggleLineItem(getContext(),
-                getString(R.string.zone_auto),
-                getString(R.string.zone_auto_summary),
-                Settings.Global.AUTO_TIME_ZONE));
-        mListItems.add(new SetDateLineItem(getContext(), getFragmentController()));
-        mListItems.add(new SetTimeLineItem(getContext(), getFragmentController()));
-        mListItems.add(new SetTimeZoneLineItem(getContext(), getFragmentController()));
-        mListItems.add(new TimeFormatToggleLineItem(getContext()));
-        return mListItems;
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        getActivity().registerReceiver(mTimeChangedBroadCastReceiver, TIME_CHANGED_FILTER);
-        refreshList();
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        getActivity().unregisterReceiver(mTimeChangedBroadCastReceiver);
-    }
-
-    private class TimeChangedBroadCastReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            for (ListItem listItem : mListItems) {
-                if (!(listItem instanceof ListRefreshObserver)) {
-                    throw new IllegalArgumentException(
-                            "all list items should be ListRefreshObserver");
-                }
-                ((ListRefreshObserver) listItem).onPreRefresh();
-            }
-            refreshList();
-        }
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.datetime_settings_fragment;
     }
 }
diff --git a/src/com/android/car/settings/datetime/SetDateLineItem.java b/src/com/android/car/settings/datetime/SetDateLineItem.java
deleted file mode 100644
index 1705632..0000000
--- a/src/com/android/car/settings/datetime/SetDateLineItem.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.datetime;
-
-import android.content.Context;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.BaseFragment.FragmentController;
-
-import java.util.Calendar;
-
-/**
- * A LineItem that displays and sets system date.
- */
-class SetDateLineItem extends TextListItem implements DatetimeSettingsFragment.ListRefreshObserver {
-
-    private final Context mContext;
-    private final FragmentController mFragmentController;
-
-    public SetDateLineItem(Context context, BaseFragment.FragmentController fragmentController) {
-        super(context);
-        mContext = context;
-        mFragmentController = fragmentController;
-        setTitle(context.getString(R.string.date_time_set_date));
-        updateLineItemData();
-    }
-
-    @Override
-    public void onPreRefresh() {
-        updateLineItemData();
-    }
-
-    private void updateLineItemData() {
-        setBody(DateFormat.getLongDateFormat(mContext).format(Calendar.getInstance().getTime()));
-        if (isEnabled()) {
-            setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-            setOnClickListener(v ->
-                    mFragmentController.launchFragment(DatePickerFragment.getInstance()));
-        } else {
-            setSupplementalIcon(null, /* showDivider= */ false);
-            setOnClickListener(null);
-        }
-    }
-
-    private boolean isEnabled() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.AUTO_TIME, 0) <= 0;
-    }
-}
diff --git a/src/com/android/car/settings/datetime/SetTimeLineItem.java b/src/com/android/car/settings/datetime/SetTimeLineItem.java
deleted file mode 100644
index 9deef6d..0000000
--- a/src/com/android/car/settings/datetime/SetTimeLineItem.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.datetime;
-
-import android.content.Context;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.BaseFragment.FragmentController;
-
-import java.util.Calendar;
-
-/**
- * A LineItem that displays and sets system time.
- */
-class SetTimeLineItem extends TextListItem implements DatetimeSettingsFragment.ListRefreshObserver {
-
-    private final Context mContext;
-    private final FragmentController mFragmentController;
-
-    public SetTimeLineItem(Context context, BaseFragment.FragmentController fragmentController) {
-        super(context);
-        mContext = context;
-        mFragmentController = fragmentController;
-        setTitle(context.getString(R.string.date_time_set_time));
-        updateLineItemData();
-    }
-
-    @Override
-    public void onPreRefresh() {
-        updateLineItemData();
-    }
-
-    private void updateLineItemData() {
-        setBody(DateFormat.getTimeFormat(mContext).format(Calendar.getInstance().getTime()));
-        if (isEnabled()) {
-            setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-            setOnClickListener(v ->
-                    mFragmentController.launchFragment(TimePickerFragment.getInstance()));
-        } else {
-            setSupplementalIcon(null, /* showDivider= */ false);
-            setOnClickListener(null);
-        }
-    }
-
-    private boolean isEnabled() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.AUTO_TIME, 0) <= 0;
-    }
-}
diff --git a/src/com/android/car/settings/datetime/SetTimeZoneLineItem.java b/src/com/android/car/settings/datetime/SetTimeZoneLineItem.java
deleted file mode 100644
index c2ae8d4..0000000
--- a/src/com/android/car/settings/datetime/SetTimeZoneLineItem.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.datetime;
-
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.BaseFragment.FragmentController;
-import com.android.settingslib.datetime.ZoneGetter;
-
-import java.util.Calendar;
-
-/**
- * A LineItem that displays and sets time zone.
- */
-class SetTimeZoneLineItem extends TextListItem
-        implements DatetimeSettingsFragment.ListRefreshObserver {
-
-    private final Context mContext;
-    private final FragmentController mFragmentController;
-
-    public SetTimeZoneLineItem(Context context, BaseFragment.FragmentController fragmentController) {
-        super(context);
-        mContext = context;
-        mFragmentController = fragmentController;
-        setTitle(context.getString(R.string.date_time_set_timezone));
-        setBody(getDesc());
-        updateLineItemData();
-    }
-
-    @Override
-    public void onPreRefresh() {
-        updateLineItemData();
-    }
-
-    private void updateLineItemData() {
-        if (isEnabled()) {
-            setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-            setOnClickListener(v ->
-                    mFragmentController.launchFragment(TimeZonePickerFragment.getInstance()));
-        } else {
-            setSupplementalIcon(null, /* showDivider= */ false);
-            setOnClickListener(null);
-        }
-    }
-
-    private String getDesc() {
-        Calendar now = Calendar.getInstance();
-        return ZoneGetter.getTimeZoneOffsetAndName(mContext, now.getTimeZone(), now.getTime())
-                .toString();
-    }
-
-    private boolean isEnabled() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.AUTO_TIME_ZONE, 0) <= 0;
-    }
-}
diff --git a/src/com/android/car/settings/datetime/TimeFormatToggleLineItem.java b/src/com/android/car/settings/datetime/TimeFormatToggleLineItem.java
deleted file mode 100644
index 8174a4b..0000000
--- a/src/com/android/car/settings/datetime/TimeFormatToggleLineItem.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.datetime;
-
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-
-import java.util.Calendar;
-
-/**
- * A LineItem that displays and sets system time format.
- */
-class TimeFormatToggleLineItem extends TextListItem
-        implements DatetimeSettingsFragment.ListRefreshObserver {
-    private static final String HOURS_12 = "12";
-    private static final String HOURS_24 = "24";
-    private static final int DEMO_YEAR = 2017;
-    private static final int DEMO_MONTH = 11;
-    private static final int DEMO_DAY_OF_MONTH = 31;
-    private static final int DEMO_HOUR_OF_DAY = 13;
-    private static final int DEMO_MINUTE = 0;
-    private static final int DEMO_SECOND = 0;
-
-    private final Calendar mTimeFormatDemoDate = Calendar.getInstance();
-
-    private Context mContext;
-
-    public TimeFormatToggleLineItem(Context context) {
-        super(context);
-        setTitle(context.getString(R.string.date_time_24hour));
-        mContext = context;
-        mTimeFormatDemoDate.set(
-                DEMO_YEAR,
-                DEMO_MONTH,
-                DEMO_DAY_OF_MONTH,
-                DEMO_HOUR_OF_DAY,
-                DEMO_MINUTE,
-                DEMO_SECOND);
-        setBody(getDesc());
-        setSwitch(
-                isChecked(),
-                /* showDivider= */ false,
-                (button, is24Hour) -> {
-                    Settings.System.putString(mContext.getContentResolver(),
-                            Settings.System.TIME_12_24,
-                            is24Hour ? HOURS_24 : HOURS_12);
-                    Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
-                    int timeFormatPreference =
-                            is24Hour ? Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR
-                                    : Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR;
-                    timeChanged.putExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
-                            timeFormatPreference);
-                    mContext.sendBroadcast(timeChanged);
-                });
-    }
-
-    @Override
-    public void onPreRefresh() {
-        setBody(getDesc());
-        setSwitchState(isChecked());
-    }
-
-    private boolean isChecked() {
-        return DateFormat.is24HourFormat(mContext);
-    }
-
-    private String getDesc() {
-        return DateFormat.getTimeFormat(mContext)
-                .format(mTimeFormatDemoDate.getTime());
-    }
-}
diff --git a/src/com/android/car/settings/datetime/TimeFormatTogglePreferenceController.java b/src/com/android/car/settings/datetime/TimeFormatTogglePreferenceController.java
new file mode 100644
index 0000000..74e73a1
--- /dev/null
+++ b/src/com/android/car/settings/datetime/TimeFormatTogglePreferenceController.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.Calendar;
+
+/**
+ * Business logic for toggle which chooses between 12 hour or 24 hour formats.
+ */
+public class TimeFormatTogglePreferenceController extends PreferenceController<TwoStatePreference> {
+    public static final String HOURS_12 = "12";
+    public static final String HOURS_24 = "24";
+
+    private static final int DEMO_MONTH = 11;
+    private static final int DEMO_DAY_OF_MONTH = 31;
+    private static final int DEMO_HOUR_OF_DAY = 13;
+    private static final int DEMO_MINUTE = 0;
+    private static final int DEMO_SECOND = 0;
+    private final Calendar mTimeFormatDemoDate = Calendar.getInstance();
+    private final BroadcastReceiver mTimeChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshUi();
+        }
+    };
+
+    public TimeFormatTogglePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    /** Starts the broadcast receiver which listens for time changes */
+    @Override
+    protected void onStartInternal() {
+        // Listens to ACTION_TIME_CHANGED because the description needs to be changed based on
+        // the ACTION_TIME_CHANGED intent that this toggle sends.
+        getContext().registerReceiver(mTimeChangeReceiver,
+                new IntentFilter(Intent.ACTION_TIME_CHANGED));
+    }
+
+    /** Stops the broadcast receiver which listens for time changes */
+    @Override
+    protected void onStopInternal() {
+        getContext().unregisterReceiver(mTimeChangeReceiver);
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        Calendar now = Calendar.getInstance();
+        mTimeFormatDemoDate.setTimeZone(now.getTimeZone());
+        // We use December 31st because it's unambiguous when demonstrating the date format.
+        // We use 13:00 so we can demonstrate the 12/24 hour options.
+        mTimeFormatDemoDate.set(now.get(Calendar.YEAR), DEMO_MONTH, DEMO_DAY_OF_MONTH,
+                DEMO_HOUR_OF_DAY, DEMO_MINUTE, DEMO_SECOND);
+        preference.setSummary(
+                DateFormat.getTimeFormat(getContext()).format(mTimeFormatDemoDate.getTime()));
+        preference.setChecked(is24Hour());
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean isUse24HourFormatEnabled = (boolean) newValue;
+        Settings.System.putString(getContext().getContentResolver(),
+                Settings.System.TIME_12_24,
+                isUse24HourFormatEnabled ? HOURS_24 : HOURS_12);
+        Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
+        int timeFormatPreference =
+                isUse24HourFormatEnabled ? Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR
+                        : Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR;
+        timeChanged.putExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+                timeFormatPreference);
+        getContext().sendBroadcast(timeChanged);
+        return true;
+    }
+
+    private boolean is24Hour() {
+        return DateFormat.is24HourFormat(getContext());
+    }
+}
diff --git a/src/com/android/car/settings/datetime/TimePickerFragment.java b/src/com/android/car/settings/datetime/TimePickerFragment.java
index 8d971f3..2e0cdcd 100644
--- a/src/com/android/car/settings/datetime/TimePickerFragment.java
+++ b/src/com/android/car/settings/datetime/TimePickerFragment.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.car.settings.datetime;
@@ -23,6 +23,9 @@
 import android.widget.Button;
 import android.widget.TimePicker;
 
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
+
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
 
@@ -36,14 +39,22 @@
 
     private TimePicker mTimePicker;
 
-    public static TimePickerFragment getInstance() {
-        TimePickerFragment timePickerFragment = new TimePickerFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.time_picker_title);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.time_picker);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        timePickerFragment.setArguments(bundle);
-        return timePickerFragment;
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return R.layout.time_picker;
+    }
+
+    @Override
+    @StringRes
+    protected int getTitleId() {
+        return R.string.time_picker_title;
     }
 
     @Override
diff --git a/src/com/android/car/settings/datetime/TimePickerPreferenceController.java b/src/com/android/car/settings/datetime/TimePickerPreferenceController.java
new file mode 100644
index 0000000..c4bdc97
--- /dev/null
+++ b/src/com/android/car/settings/datetime/TimePickerPreferenceController.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.Calendar;
+
+/**
+ * Business logic for the preference which allows for time selection.
+ */
+public class TimePickerPreferenceController extends PreferenceController<Preference> {
+
+    private final IntentFilter mIntentFilter;
+    private final BroadcastReceiver mTimeChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshUi();
+        }
+    };
+
+    public TimePickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        // ACTION_TIME_CHANGED listens to changes to the autoDatetime toggle to update the time.
+        // ACTION_TIME_TICK listens to the minute changes to update the shown time.
+        // ACTION_TIMEZONE_CHANGED listens to time zone changes to update the shown time.
+        mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(Intent.ACTION_TIME_CHANGED);
+        mIntentFilter.addAction(Intent.ACTION_TIME_TICK);
+        mIntentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /** Starts the broadcast receiver which listens for time changes */
+    @Override
+    protected void onStartInternal() {
+        getContext().registerReceiver(mTimeChangeReceiver, mIntentFilter);
+    }
+
+    /** Stops the broadcast receiver which listens for time changes */
+    @Override
+    protected void onStopInternal() {
+        getContext().unregisterReceiver(mTimeChangeReceiver);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        preference.setSummary(
+                DateFormat.getTimeFormat(getContext()).format(Calendar.getInstance().getTime()));
+        preference.setEnabled(!autoDatetimeIsEnabled());
+    }
+
+    private boolean autoDatetimeIsEnabled() {
+        return Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.AUTO_TIME, 0) > 0;
+    }
+}
diff --git a/src/com/android/car/settings/datetime/TimeZoneLineItem.java b/src/com/android/car/settings/datetime/TimeZoneLineItem.java
deleted file mode 100644
index 5813a09..0000000
--- a/src/com/android/car/settings/datetime/TimeZoneLineItem.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.datetime;
-
-import android.annotation.NonNull;
-import android.app.AlarmManager;
-import android.content.Context;
-
-import androidx.car.widget.TextListItem;
-
-import com.android.settingslib.datetime.ZoneGetter;
-
-import java.util.Map;
-
-/**
- * A LineItem that displays available time zone.
- */
-class TimeZoneLineItem extends TextListItem {
-
-    public interface TimeZoneChangeListener {
-        void onTimeZoneChanged();
-    }
-
-    public TimeZoneLineItem(
-            Context context,
-            @NonNull TimeZoneChangeListener listener,
-            Map<String, Object> timeZone) {
-        super(context);
-        setTitle(timeZone.get(ZoneGetter.KEY_DISPLAYNAME).toString());
-        setBody(timeZone.get(ZoneGetter.KEY_GMT).toString());
-        setOnClickListener(v -> {
-            AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-            am.setTimeZone((String) timeZone.get(ZoneGetter.KEY_ID));
-            listener.onTimeZoneChanged();
-        });
-    }
-}
diff --git a/src/com/android/car/settings/datetime/TimeZonePickerFragment.java b/src/com/android/car/settings/datetime/TimeZonePickerFragment.java
deleted file mode 100644
index 2bb3ccc..0000000
--- a/src/com/android/car/settings/datetime/TimeZonePickerFragment.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.car.settings.datetime;
-
-
-import android.content.Intent;
-import android.os.Bundle;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.settingslib.datetime.ZoneGetter;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Lists all time zone and its offset from GMT.
- */
-public class TimeZonePickerFragment extends ListItemSettingsFragment implements
-        TimeZoneLineItem.TimeZoneChangeListener {
-    private List<Map<String, Object>> mZoneList;
-
-    public static TimeZonePickerFragment getInstance() {
-        TimeZonePickerFragment timeZonePickerFragment = new TimeZonePickerFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.date_time_set_timezone_title);
-        timeZonePickerFragment.setArguments(bundle);
-        return timeZonePickerFragment;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        mZoneList = ZoneGetter.getZonesList(getContext());
-        super.onActivityCreated(savedInstanceState);
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        return new ListItemProvider.ListProvider(getListItems());
-    }
-
-    private List<ListItem> getListItems() {
-        List<ListItem> lineItems = new ArrayList<>();
-        for (Map<String, Object> zone : mZoneList) {
-            lineItems.add(new TimeZoneLineItem(getContext(), this, zone));
-        }
-        return lineItems;
-    }
-
-    @Override
-    public void onTimeZoneChanged() {
-        getContext().sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
-        getFragmentController().goBack();
-    }
-}
diff --git a/src/com/android/car/settings/datetime/TimeZonePickerPreferenceController.java b/src/com/android/car/settings/datetime/TimeZonePickerPreferenceController.java
new file mode 100644
index 0000000..6a45f72
--- /dev/null
+++ b/src/com/android/car/settings/datetime/TimeZonePickerPreferenceController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.datetime.ZoneGetter;
+
+import java.util.Calendar;
+
+/**
+ * Business logic for the preference that allows for changing the timezone.
+ */
+public class TimeZonePickerPreferenceController extends PreferenceController<Preference> {
+
+    private final IntentFilter mIntentFilter;
+    private final BroadcastReceiver mTimeChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshUi();
+        }
+    };
+
+    public TimeZonePickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+
+        // Listen to ACTION_TIME_CHANGED because it could be sent by the autoTimeZone toggle.
+        // Listen to ACTION_TIMEZONE_CHANGED because the change in timezone should update the
+        // description of the preference.
+        mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(Intent.ACTION_TIME_CHANGED);
+        mIntentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /** Starts the broadcast receiver which listens for time changes */
+    @Override
+    protected void onStartInternal() {
+        getContext().registerReceiver(mTimeChangeReceiver, mIntentFilter);
+    }
+
+    /** Stops the broadcast receiver which listens for time changes */
+    @Override
+    protected void onStopInternal() {
+        getContext().unregisterReceiver(mTimeChangeReceiver);
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        Calendar now = Calendar.getInstance();
+        preference.setSummary(ZoneGetter.getTimeZoneOffsetAndName(getContext(), now.getTimeZone(),
+                now.getTime()));
+        preference.setEnabled(!autoTimezoneIsEnabled());
+    }
+
+    private boolean autoTimezoneIsEnabled() {
+        return Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.AUTO_TIME_ZONE, 0) > 0;
+    }
+}
diff --git a/src/com/android/car/settings/datetime/TimeZonePickerScreenFragment.java b/src/com/android/car/settings/datetime/TimeZonePickerScreenFragment.java
new file mode 100644
index 0000000..3b83c9e
--- /dev/null
+++ b/src/com/android/car/settings/datetime/TimeZonePickerScreenFragment.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+import java.util.TimeZone;
+
+/**
+ * Lists all time zone and its offset from GMT.
+ */
+public class TimeZonePickerScreenFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.timezone_picker_screen_fragment;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        scrollToPreference(TimeZone.getDefault().getID());
+    }
+}
diff --git a/src/com/android/car/settings/datetime/TimeZonePickerScreenPreferenceController.java b/src/com/android/car/settings/datetime/TimeZonePickerScreenPreferenceController.java
new file mode 100644
index 0000000..601225c
--- /dev/null
+++ b/src/com/android/car/settings/datetime/TimeZonePickerScreenPreferenceController.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import android.app.AlarmManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.datetime.ZoneGetter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Business logic which will populate the timezone options.
+ */
+public class TimeZonePickerScreenPreferenceController extends
+        PreferenceController<PreferenceGroup> {
+
+    private List<Preference> mZonesList;
+    @VisibleForTesting
+    AlarmManager mAlarmManager;
+
+    public TimeZonePickerScreenPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        if (mZonesList == null) {
+            constructTimeZoneList();
+        }
+        for (Preference zonePreference : mZonesList) {
+            preferenceGroup.addPreference(zonePreference);
+        }
+    }
+
+    private void constructTimeZoneList() {
+        // We load all of the time zones on the UI thread. However it shouldn't be very expensive
+        // and also shouldn't take a long time. We can revisit this to setup background work and
+        // paging, if it becomes an issue.
+        List<Map<String, Object>> zones = ZoneGetter.getZonesList(getContext());
+        setZonesList(zones);
+    }
+
+    @VisibleForTesting
+    void setZonesList(List<Map<String, Object>> zones) {
+        Collections.sort(zones, new TimeZonesComparator());
+        mZonesList = new ArrayList<>();
+        for (Map<String, Object> zone : zones) {
+            mZonesList.add(createTimeZonePreference(zone));
+        }
+    }
+
+    /** Construct a time zone preference based on the Map object given by {@link ZoneGetter}. */
+    private Preference createTimeZonePreference(Map<String, Object> timeZone) {
+        Preference preference = new Preference(getContext());
+        preference.setKey(timeZone.get(ZoneGetter.KEY_ID).toString());
+        preference.setTitle(timeZone.get(ZoneGetter.KEY_DISPLAY_LABEL).toString());
+        preference.setSummary(timeZone.get(ZoneGetter.KEY_OFFSET_LABEL).toString());
+        preference.setOnPreferenceClickListener(pref -> {
+            mAlarmManager.setTimeZone(timeZone.get(ZoneGetter.KEY_ID).toString());
+            getFragmentController().goBack();
+
+            // Note: This is intentionally ACTION_TIME_CHANGED, not ACTION_TIMEZONE_CHANGED.
+            // Timezone change is handled by the alarm manager. This broadcast message is used
+            // to update the clock and other time related displays that the time has changed due
+            // to a change in the timezone.
+            getContext().sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+            return true;
+        });
+        return preference;
+    }
+
+    /** Compares the timezone objects returned by {@link ZoneGetter}. */
+    private static final class TimeZonesComparator implements Comparator<Map<String, Object>> {
+
+        /** Compares timezones based on 1. offset, 2. display label/name. */
+        TimeZonesComparator() {
+        }
+
+        @Override
+        public int compare(Map<String, Object> map1, Map<String, Object> map2) {
+            int timeZoneOffsetCompare = compareWithKey(map1, map2, ZoneGetter.KEY_OFFSET);
+
+            // If equivalent timezone offset, compare based on display label.
+            if (timeZoneOffsetCompare == 0) {
+                return compareWithKey(map1, map2, ZoneGetter.KEY_DISPLAY_LABEL);
+            }
+
+            return timeZoneOffsetCompare;
+        }
+
+        private int compareWithKey(Map<String, Object> map1, Map<String, Object> map2,
+                String comparisonKey) {
+            Object value1 = map1.get(comparisonKey);
+            Object value2 = map2.get(comparisonKey);
+            if (!isComparable(value1) || !isComparable(value2)) {
+                throw new IllegalArgumentException(
+                        "Cannot use Map which has values that are not Comparable");
+            }
+            return ((Comparable) value1).compareTo(value2);
+        }
+
+        private boolean isComparable(Object value) {
+            return (value != null) && (value instanceof Comparable);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/development/DevelopmentSettingsUtil.java b/src/com/android/car/settings/development/DevelopmentSettingsUtil.java
new file mode 100644
index 0000000..63ded44
--- /dev/null
+++ b/src/com/android/car/settings/development/DevelopmentSettingsUtil.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.car.settings.development;
+
+import android.app.ActivityManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.car.settings.R;
+import com.android.settingslib.development.DevelopmentSettingsEnabler;
+
+/**
+ * A utility to set/check development settings mode.
+ *
+ * <p>Shared logic with {@link com.android.settingslib.development.DevelopmentSettingsEnabler} with
+ * modifications to use CarUserManagerHelper instead of UserManager.
+ */
+public class DevelopmentSettingsUtil {
+
+    private DevelopmentSettingsUtil() {
+    }
+
+    /**
+     * Sets the global toggle for developer settings and sends out a local broadcast to notify other
+     * of this change.
+     */
+    public static void setDevelopmentSettingsEnabled(Context context, boolean enable) {
+        Settings.Global.putInt(context.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, enable ? 1 : 0);
+
+        // Used to enable developer options module.
+        ComponentName targetName = ComponentName.unflattenFromString(
+                context.getString(R.string.config_dev_options_module));
+        setDeveloperOptionsEnabledState(context, targetName, showDeveloperOptions(context));
+    }
+
+    /**
+     * Checks that the development settings should be enabled. Returns true if global toggle is set,
+     * debugging is allowed for user, and the user is an admin or a demo user.
+     */
+    public static boolean isDevelopmentSettingsEnabled(Context context,
+            CarUserManagerHelper carUserManagerHelper) {
+        boolean settingEnabled = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, Build.IS_ENG ? 1 : 0) != 0;
+        boolean hasRestriction = carUserManagerHelper.hasUserRestriction(
+                UserManager.DISALLOW_DEBUGGING_FEATURES,
+                carUserManagerHelper.getCurrentProcessUserInfo());
+        boolean isAdminOrDemo = carUserManagerHelper.isCurrentProcessAdminUser()
+                || carUserManagerHelper.isCurrentProcessDemoUser();
+        return isAdminOrDemo && !hasRestriction && settingEnabled;
+    }
+
+    /** Checks whether the device is provisioned or not. */
+    public static boolean isDeviceProvisioned(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+    }
+
+    private static boolean showDeveloperOptions(Context context) {
+        CarUserManagerHelper carUserManagerHelper = new CarUserManagerHelper(context);
+        boolean showDev = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)
+                && !isMonkeyRunning();
+        boolean isAdminOrDemo = carUserManagerHelper.isCurrentProcessAdminUser()
+                || carUserManagerHelper.isCurrentProcessDemoUser();
+        if (UserHandle.MU_ENABLED && !isAdminOrDemo) {
+            showDev = false;
+        }
+
+        return showDev;
+    }
+
+    private static void setDeveloperOptionsEnabledState(Context context, ComponentName component,
+            boolean enabled) {
+        PackageManager pm = context.getPackageManager();
+        int state = pm.getComponentEnabledSetting(component);
+        boolean isEnabled = state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        if (isEnabled != enabled || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+            pm.setComponentEnabledSetting(component, enabled
+                            ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                            : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP);
+        }
+    }
+
+    private static boolean isMonkeyRunning() {
+        return ActivityManager.isUserAMonkey();
+    }
+}
diff --git a/src/com/android/car/settings/display/AdaptiveBrightnessTogglePreferenceController.java b/src/com/android/car/settings/display/AdaptiveBrightnessTogglePreferenceController.java
new file mode 100644
index 0000000..5c9cb33
--- /dev/null
+++ b/src/com/android/car/settings/display/AdaptiveBrightnessTogglePreferenceController.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 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.car.settings.display;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Business logic for controlling the adaptive brightness setting. */
+public class AdaptiveBrightnessTogglePreferenceController extends
+        PreferenceController<TwoStatePreference> {
+
+    public AdaptiveBrightnessTogglePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(isAdaptiveBrightnessChecked());
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return supportsAdaptiveBrightness() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean enableAdaptiveBrightness = (boolean) newValue;
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                enableAdaptiveBrightness ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
+                        : Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        return true;
+    }
+
+    private boolean isAdaptiveBrightnessChecked() {
+        int brightnessMode = Settings.System.getInt(getContext().getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        return brightnessMode != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
+    }
+
+    private boolean supportsAdaptiveBrightness() {
+        return getContext().getResources().getBoolean(R.bool.config_automatic_brightness_available);
+    }
+}
diff --git a/src/com/android/car/settings/display/BrightnessLevelPreferenceController.java b/src/com/android/car/settings/display/BrightnessLevelPreferenceController.java
new file mode 100644
index 0000000..02ec623
--- /dev/null
+++ b/src/com/android/car/settings/display/BrightnessLevelPreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 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.car.settings.display;
+
+import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX;
+import static com.android.settingslib.display.BrightnessUtils.convertGammaToLinear;
+import static com.android.settingslib.display.BrightnessUtils.convertLinearToGamma;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.PowerManager;
+import android.provider.Settings;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.SeekBarPreference;
+
+/** Business logic for changing the brightness of the display. */
+public class BrightnessLevelPreferenceController extends PreferenceController<SeekBarPreference> {
+
+    private static final Logger LOG = new Logger(BrightnessLevelPreferenceController.class);
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final int mMaximumBacklight;
+    private final int mMinimumBacklight;
+
+    public BrightnessLevelPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mMaximumBacklight = powerManager.getMaximumScreenBrightnessSetting();
+        mMinimumBacklight = powerManager.getMinimumScreenBrightnessSetting();
+    }
+
+    @Override
+    protected Class<SeekBarPreference> getPreferenceType() {
+        return SeekBarPreference.class;
+    }
+
+    @Override
+    protected void updateState(SeekBarPreference preference) {
+        preference.setMax(GAMMA_SPACE_MAX);
+        preference.setValue(getSeekbarValue());
+        preference.setContinuousUpdate(true);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(SeekBarPreference preference, Object newValue) {
+        int gamma = (Integer) newValue;
+        int linear = convertGammaToLinear(gamma, mMinimumBacklight, mMaximumBacklight);
+        Settings.System.putIntForUser(getContext().getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS, linear,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+        return true;
+    }
+
+    private int getSeekbarValue() {
+        int gamma = GAMMA_SPACE_MAX;
+        try {
+            int linear = Settings.System.getIntForUser(getContext().getContentResolver(),
+                    Settings.System.SCREEN_BRIGHTNESS,
+                    mCarUserManagerHelper.getCurrentProcessUserId());
+            gamma = convertLinearToGamma(linear, mMinimumBacklight, mMaximumBacklight);
+        } catch (Settings.SettingNotFoundException e) {
+            LOG.w("Can't find setting for SCREEN_BRIGHTNESS.");
+        }
+        return gamma;
+    }
+}
diff --git a/src/com/android/car/settings/display/BrightnessLineItem.java b/src/com/android/car/settings/display/BrightnessLineItem.java
deleted file mode 100644
index f89527e..0000000
--- a/src/com/android/car/settings/display/BrightnessLineItem.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.display;
-
-import static android.provider.Settings.System.SCREEN_BRIGHTNESS;
-
-import android.content.Context;
-import android.provider.Settings;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-
-import androidx.car.widget.SeekbarListItem;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.Logger;
-
-/**
- * A LineItem that displays and sets display brightness.
- */
-public class BrightnessLineItem extends SeekbarListItem {
-    private static final Logger LOG = new Logger(BrightnessLineItem.class);
-    private static final int MAX_BRIGHTNESS = 255;
-    private final Context mContext;
-
-    /**
-     * Handles brightness change from user
-     */
-    private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
-            new OnSeekBarChangeListener() {
-                @Override
-                public void onStartTrackingTouch(SeekBar seekBar) {
-                    // no-op
-                }
-
-                @Override
-                public void onStopTrackingTouch(SeekBar seekBar) {
-                    // no-op
-                }
-
-                @Override
-                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                    Settings.System.putInt(
-                            mContext.getContentResolver(), SCREEN_BRIGHTNESS, progress);
-                }
-            };
-
-    public BrightnessLineItem(Context context) {
-        super(context);
-        mContext = context;
-        setMax(MAX_BRIGHTNESS);
-        setProgress(getSeekbarValue(context));
-        setOnSeekBarChangeListener(mOnSeekBarChangeListener);
-        setText(context.getString(R.string.brightness));
-    }
-
-    private static int getSeekbarValue(Context context) {
-        int currentBrightness = 0;
-        try {
-            currentBrightness = Settings.System.getInt(context.getContentResolver(),
-                    SCREEN_BRIGHTNESS);
-        } catch (Settings.SettingNotFoundException e) {
-            LOG.w("Can't find setting for SCREEN_BRIGHTNESS.");
-        }
-        return currentBrightness;
-    }
-}
diff --git a/src/com/android/car/settings/display/DisplaySettingsFragment.java b/src/com/android/car/settings/display/DisplaySettingsFragment.java
index f808906..97e5359 100644
--- a/src/com/android/car/settings/display/DisplaySettingsFragment.java
+++ b/src/com/android/car/settings/display/DisplaySettingsFragment.java
@@ -15,77 +15,19 @@
  */
 package com.android.car.settings.display;
 
-import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
-import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
-import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.provider.Settings;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.ListItemProvider.ListProvider;
-import androidx.car.widget.TextListItem;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.android.car.settings.common.SettingsFragment;
 
 /**
- * Activity to host Display related preferences.
+ * Preference fragment to host Display related preferences.
  */
-public class DisplaySettingsFragment extends ListItemSettingsFragment {
-
-    /**
-     * Gets a new instance of this class.
-     */
-    public static DisplaySettingsFragment newInstance() {
-        DisplaySettingsFragment displaySettingsFragment = new DisplaySettingsFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.display_settings);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar);
-        displaySettingsFragment.setArguments(bundle);
-        return displaySettingsFragment;
-    }
+public class DisplaySettingsFragment extends SettingsFragment {
 
     @Override
-    public ListItemProvider getItemProvider() {
-        return new ListProvider(getLineItems());
-    }
-
-    private List<ListItem> getLineItems() {
-        List<ListItem> lineItems = new ArrayList<>();
-        Context context = getContext();
-        if (supportsAdaptiveBrightness()) {
-            TextListItem adaptiveBrightnessItem = new TextListItem(context);
-            adaptiveBrightnessItem.setTitle(context.getString(R.string.auto_brightness_title));
-            adaptiveBrightnessItem.setBody(
-                    context.getString(R.string.auto_brightness_summary));
-            adaptiveBrightnessItem.setSwitch(
-                    isAdaptiveBrightnessChecked(),
-                    /* showDivider= */false,
-                    (button, isChecked) ->
-                            Settings.System.putInt(context.getContentResolver(),
-                                    SCREEN_BRIGHTNESS_MODE,
-                                    isChecked ? SCREEN_BRIGHTNESS_MODE_AUTOMATIC
-                                            : SCREEN_BRIGHTNESS_MODE_MANUAL));
-            lineItems.add(adaptiveBrightnessItem);
-        }
-        lineItems.add(new BrightnessLineItem(context));
-        return lineItems;
-    }
-
-    private boolean isAdaptiveBrightnessChecked() {
-        int brightnessMode = Settings.System.getInt(getContext().getContentResolver(),
-                SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_MANUAL);
-        return brightnessMode != SCREEN_BRIGHTNESS_MODE_MANUAL;
-    }
-
-    private boolean supportsAdaptiveBrightness() {
-        return getContext().getResources().getBoolean(
-                com.android.internal.R.bool.config_automatic_brightness_available);
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.display_settings_fragment;
     }
 }
diff --git a/src/com/android/car/settings/home/BluetoothLineItem.java b/src/com/android/car/settings/home/BluetoothLineItem.java
deleted file mode 100644
index c9ee22d..0000000
--- a/src/com/android/car/settings/home/BluetoothLineItem.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.home;
-
-import android.annotation.DrawableRes;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.Switch;
-
-import com.android.car.list.IconToggleLineItem;
-import com.android.car.settings.R;
-import com.android.car.settings.bluetooth.BluetoothSettingsFragment;
-import com.android.car.settings.common.BaseFragment;
-
-
-/**
- * Represents the Bluetooth line item on settings home page.
- */
-public class BluetoothLineItem extends IconToggleLineItem {
-    private BluetoothAdapter mBluetoothAdapter;
-    private BaseFragment.FragmentController mFragmentController;
-
-    public BluetoothLineItem(Context context, BaseFragment.FragmentController fragmentController) {
-        super(context.getText(R.string.bluetooth_settings), context);
-        mFragmentController = fragmentController;
-        mBluetoothAdapter =
-                ((BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE))
-                        .getAdapter();
-    }
-
-    @Override
-    public boolean onToggleTouched(Switch toggleSwitch, MotionEvent event) {
-        if (!isBluetoothAvailable()) {
-            return true;
-        }
-        // intercept touch event, so we can process the request and update the switch
-        // state accordingly
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            if (isChecked()) {
-                mBluetoothAdapter.disable();
-            } else {
-                mBluetoothAdapter.enable();
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public boolean isExpandable() {
-        return false;
-    }
-
-    @Override
-    public boolean isClickable() {
-        return isBluetoothAvailable();
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (isBluetoothAvailable()) {
-            mFragmentController.launchFragment(BluetoothSettingsFragment.getInstance());
-        }
-    }
-
-    @Override
-    public CharSequence getDesc() {
-        return mContext.getText(R.string.bluetooth_settings_summary);
-    }
-
-    @Override
-    public boolean isChecked() {
-        return isBluetoothAvailable() && mBluetoothAdapter.isEnabled();
-    }
-
-    @Override
-    public @DrawableRes int getIcon() {
-        return getIconRes(isBluetoothAvailable() && mBluetoothAdapter.isEnabled());
-    }
-
-    public void onBluetoothStateChanged(boolean enabled) {
-        if (mIconUpdateListener != null) {
-            mIconUpdateListener.onUpdateIcon(getIconRes(enabled));
-        }
-        if (mSwitchStateUpdateListener != null) {
-            mSwitchStateUpdateListener.onToggleChanged(enabled);
-        }
-    }
-
-    private boolean isBluetoothAvailable() {
-        return mBluetoothAdapter != null;
-    }
-
-    private @DrawableRes int getIconRes(boolean enabled) {
-        return enabled
-                ? R.drawable.ic_settings_bluetooth : R.drawable.ic_settings_bluetooth_disabled;
-    }
-}
diff --git a/src/com/android/car/settings/home/ExtraSettingsLoader.java b/src/com/android/car/settings/home/ExtraSettingsLoader.java
deleted file mode 100644
index ea0ba35..0000000
--- a/src/com/android/car/settings/home/ExtraSettingsLoader.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package com.android.car.settings.home;
-
-import static com.android.settingslib.drawer.TileUtils.EXTRA_SETTINGS_ACTION;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import com.android.car.list.LaunchAppLineItem;
-import com.android.car.list.TypedPagedListAdapter;
-import com.android.car.settings.R;
-import com.android.car.settings.common.Logger;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Loads Activity with TileUtils.EXTRA_SETTINGS_ACTION.
- * TODO: remove after all list new switched to androidx.car.widget
- */
-public class ExtraSettingsLoader {
-    private static final Logger LOG = new Logger(ExtraSettingsLoader.class);
-    private static final String META_DATA_PREFERENCE_CATEGORY = "com.android.settings.category";
-    public static final String WIRELESS_CATEGORY = "com.android.settings.category.wireless";
-    public static final String DEVICE_CATEGORY = "com.android.settings.category.device";
-    public static final String SYSTEM_CATEGORY = "com.android.settings.category.system";
-    public static final String PERSONAL_CATEGORY = "com.android.settings.category.personal";
-    private final Context mContext;
-
-    public ExtraSettingsLoader(Context context) {
-        mContext = context;
-    }
-
-    public Map<String, Collection<TypedPagedListAdapter.LineItem>> load() {
-        PackageManager pm = mContext.getPackageManager();
-        Intent intent = new Intent(EXTRA_SETTINGS_ACTION);
-        Map<String, Collection<TypedPagedListAdapter.LineItem>> extraSettings = new HashMap<>();
-        // initialize the categories
-        extraSettings.put(WIRELESS_CATEGORY, new LinkedList());
-        extraSettings.put(DEVICE_CATEGORY, new LinkedList());
-        extraSettings.put(SYSTEM_CATEGORY, new LinkedList());
-        extraSettings.put(PERSONAL_CATEGORY, new LinkedList());
-
-        List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
-                PackageManager.GET_META_DATA, ActivityManager.getCurrentUser());
-
-        for (ResolveInfo resolved : results) {
-            if (!resolved.system) {
-                // Do not allow any app to add to settings, only system ones.
-                continue;
-            }
-            String title = null;
-            String summary = null;
-            String category = null;
-            ActivityInfo activityInfo = resolved.activityInfo;
-            Bundle metaData = activityInfo.metaData;
-            try {
-                Resources res = pm.getResourcesForApplication(activityInfo.packageName);
-                if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
-                    if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
-                        title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
-                    } else {
-                        title = metaData.getString(META_DATA_PREFERENCE_TITLE);
-                    }
-                }
-                if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
-                    if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
-                        summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
-                    } else {
-                        summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
-                    }
-                } else {
-                    LOG.d("no description.");
-                }
-                if (metaData.containsKey(META_DATA_PREFERENCE_CATEGORY)) {
-                    if (metaData.get(META_DATA_PREFERENCE_CATEGORY) instanceof Integer) {
-                        category = res.getString(metaData.getInt(META_DATA_PREFERENCE_CATEGORY));
-                    } else {
-                        category = metaData.getString(META_DATA_PREFERENCE_CATEGORY);
-                    }
-                } else {
-                    LOG.d("no category.");
-                }
-            } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
-                LOG.d("Couldn't find info", e);
-            }
-            if (TextUtils.isEmpty(title)) {
-                LOG.d("no title.");
-                title = activityInfo.loadLabel(pm).toString();
-            }
-            Integer iconRes = null;
-            Icon icon = null;
-            if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
-                iconRes = metaData.getInt(META_DATA_PREFERENCE_ICON);
-                icon = Icon.createWithResource(activityInfo.packageName, iconRes);
-            } else {
-                icon = Icon.createWithResource(mContext, R.drawable.ic_settings_gear);
-                LOG.d("no icon.");
-            }
-            Intent extraSettingIntent =
-                    new Intent().setClassName(activityInfo.packageName, activityInfo.name);
-            if (category == null || !extraSettings.containsKey(category)) {
-                // If category is not specified or not supported, default to device.
-                category = DEVICE_CATEGORY;
-            }
-            extraSettings.get(category).add(new LaunchAppLineItem(
-                    title, icon, mContext, summary, extraSettingIntent));
-        }
-        return extraSettings;
-    }
-}
diff --git a/src/com/android/car/settings/home/HomepageFragment.java b/src/com/android/car/settings/home/HomepageFragment.java
index d7d286b..9ca03ae 100644
--- a/src/com/android/car/settings/home/HomepageFragment.java
+++ b/src/com/android/car/settings/home/HomepageFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -11,244 +11,34 @@
  * 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
+ * limitations under the License.
  */
+
 package com.android.car.settings.home;
 
-
-import static com.android.car.settings.home.ExtraSettingsLoader.DEVICE_CATEGORY;
-import static com.android.car.settings.home.ExtraSettingsLoader.PERSONAL_CATEGORY;
-import static com.android.car.settings.home.ExtraSettingsLoader.WIRELESS_CATEGORY;
-
-import android.bluetooth.BluetoothAdapter;
-import android.car.user.CarUserManagerHelper;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
 
-import com.android.car.list.LaunchAppLineItem;
-import com.android.car.list.TypedPagedListAdapter;
+import androidx.annotation.XmlRes;
+
 import com.android.car.settings.R;
-import com.android.car.settings.accounts.AccountsListFragment;
-import com.android.car.settings.applications.ApplicationSettingsFragment;
-import com.android.car.settings.common.ListSettingsFragment;
-import com.android.car.settings.common.Logger;
-import com.android.car.settings.datetime.DatetimeSettingsFragment;
-import com.android.car.settings.display.DisplaySettingsFragment;
-import com.android.car.settings.security.SettingsScreenLockActivity;
-import com.android.car.settings.sound.SoundSettingsFragment;
-import com.android.car.settings.suggestions.SettingsSuggestionsController;
-import com.android.car.settings.system.SystemSettingsFragment;
-import com.android.car.settings.users.UsersListFragment;
-import com.android.car.settings.wifi.CarWifiManager;
-import com.android.car.settings.wifi.WifiUtil;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Map;
+import com.android.car.settings.common.SettingsFragment;
 
 /**
  * Homepage for settings for car.
  */
-public class HomepageFragment extends ListSettingsFragment implements
-        CarWifiManager.Listener,
-        SettingsSuggestionsController.Listener {
-    private static final Logger LOG = new Logger(HomepageFragment.class);
+public class HomepageFragment extends SettingsFragment {
 
-    private SettingsSuggestionsController mSettingsSuggestionsController;
-    private CarWifiManager mCarWifiManager;
-    private WifiLineItem mWifiLineItem;
-    private BluetoothLineItem mBluetoothLineItem;
-    private CarUserManagerHelper mCarUserManagerHelper;
-    // This tracks the number of suggestions currently shown in the fragment. This is based off of
-    // the assumption that suggestions are 0 through (num suggestions - 1) in the adapter. Do not
-    // change this assumption without updating the code in onSuggestionLoaded.
-    private int mNumSettingsSuggestions;
-
-    private final BroadcastReceiver mBtStateReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-
-            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
-                        BluetoothAdapter.ERROR);
-                switch (state) {
-                    case BluetoothAdapter.STATE_TURNING_OFF:
-                        // TODO show a different status icon?
-                    case BluetoothAdapter.STATE_OFF:
-                        mBluetoothLineItem.onBluetoothStateChanged(false);
-                        break;
-                    default:
-                        mBluetoothLineItem.onBluetoothStateChanged(true);
-                }
-            }
-        }
-    };
-
-    private final IntentFilter mBtStateChangeFilter =
-            new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
-
-    /**
-     * Gets an instance of this class.
-     */
-    public static HomepageFragment newInstance() {
-        HomepageFragment homepageFragment = new HomepageFragment();
-        Bundle bundle = ListSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.settings_label);
-        homepageFragment.setArguments(bundle);
-        return homepageFragment;
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.homepage_fragment;
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        LOG.v("onActivityCreated: " + savedInstanceState);
-        mSettingsSuggestionsController =
-                new SettingsSuggestionsController(
-                        getContext(),
-                        getLoaderManager(),
-                        /* listener= */ this);
-        mCarWifiManager = new CarWifiManager(getContext(), /* listener= */ this);
-        if (WifiUtil.isWifiAvailable(getContext())) {
-            mWifiLineItem = new WifiLineItem(
-                    getContext(), mCarWifiManager, getFragmentController());
-        }
-        mBluetoothLineItem = new BluetoothLineItem(getContext(), getFragmentController());
-        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
-
-        // reset the suggestion count.
-        mNumSettingsSuggestions = 0;
-
-        // Call super after the wifiLineItem and BluetoothLineItem are setup, because
-        // those are needed in super.onCreate().
-        super.onActivityCreated(savedInstanceState);
-    }
-
-    @Override
-    public void onAccessPointsChanged() {
-        // don't care
-    }
-
-    @Override
-    public void onWifiStateChanged(int state) {
-        mWifiLineItem.onWifiStateChanged(state);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mCarWifiManager.start();
-        mSettingsSuggestionsController.start();
-        getActivity().registerReceiver(mBtStateReceiver, mBtStateChangeFilter);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        mCarWifiManager.stop();
-        mSettingsSuggestionsController.stop();
-        getActivity().unregisterReceiver(mBtStateReceiver);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mCarWifiManager.destroy();
-    }
-
-    @Override
-    public ArrayList<TypedPagedListAdapter.LineItem> getLineItems() {
-        ExtraSettingsLoader extraSettingsLoader = new ExtraSettingsLoader(getContext());
-        Map<String, Collection<TypedPagedListAdapter.LineItem>> extraSettings =
-                extraSettingsLoader.load();
-        ArrayList<TypedPagedListAdapter.LineItem> lineItems = new ArrayList<>();
-        lineItems.add(new SimpleIconTransitionLineItem(
-                R.string.display_settings,
-                R.drawable.ic_settings_display,
-                getContext(),
-                null,
-                DisplaySettingsFragment.newInstance(),
-                getFragmentController()));
-        lineItems.add(new SimpleIconTransitionLineItem(
-                R.string.sound_settings,
-                R.drawable.ic_settings_sound,
-                getContext(),
-                null,
-                SoundSettingsFragment.newInstance(),
-                getFragmentController()));
-        if (mWifiLineItem != null) {
-            lineItems.add(mWifiLineItem);
-        }
-        lineItems.addAll(extraSettings.get(WIRELESS_CATEGORY));
-        lineItems.add(mBluetoothLineItem);
-        lineItems.add(new SimpleIconTransitionLineItem(
-                R.string.applications_settings,
-                R.drawable.ic_settings_applications,
-                getContext(),
-                null,
-                ApplicationSettingsFragment.newInstance(),
-                getFragmentController()));
-        lineItems.add(new SimpleIconTransitionLineItem(
-                R.string.date_and_time_settings_title,
-                R.drawable.ic_settings_date_time,
-                getContext(),
-                null,
-                DatetimeSettingsFragment.getInstance(),
-                getFragmentController()));
-        lineItems.add(new SimpleIconTransitionLineItem(
-                R.string.users_list_title,
-                R.drawable.ic_user,
-                getContext(),
-                null,
-                UsersListFragment.newInstance(),
-                getFragmentController()));
-
-        // Guest users can't set screen locks or add/remove accounts.
-        if (!mCarUserManagerHelper.isCurrentProcessGuestUser()) {
-            lineItems.add(new SimpleIconTransitionLineItem(
-                    R.string.accounts_settings_title,
-                    R.drawable.ic_account,
-                    getContext(),
-                    null,
-                    AccountsListFragment.newInstance(),
-                    getFragmentController()));
-            lineItems.add(new LaunchAppLineItem(
-                    getString(R.string.security_settings_title),
-                    Icon.createWithResource(getContext(), R.drawable.ic_lock),
-                    getContext(),
-                    null,
-                    new Intent(getContext(), SettingsScreenLockActivity.class)));
-        }
-
-        lineItems.add(new SimpleIconTransitionLineItem(
-                R.string.system_setting_title,
-                R.drawable.ic_settings_about,
-                getContext(),
-                null,
-                SystemSettingsFragment.getInstance(),
-                getFragmentController()));
-
-        lineItems.addAll(extraSettings.get(DEVICE_CATEGORY));
-        lineItems.addAll(extraSettings.get(PERSONAL_CATEGORY));
-        return lineItems;
-    }
-
-    @Override
-    public void onSuggestionsLoaded(ArrayList<TypedPagedListAdapter.LineItem> suggestions) {
-        LOG.v("onDeferredSuggestionsLoaded");
-        for (int index = 0; index < mNumSettingsSuggestions; index++) {
-            mPagedListAdapter.remove(0);
-        }
-        mNumSettingsSuggestions = suggestions.size();
-        mPagedListAdapter.addAll(0, suggestions);
-    }
-
-    @Override
-    public void onSuggestionDismissed(int adapterPosition) {
-        LOG.v("onSuggestionDismissed adapterPosition " + adapterPosition);
-        mPagedListAdapter.remove(adapterPosition);
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        // TODO: Re-enable suggestions once more use cases are supported.
+        // use(SuggestionsPreferenceController.class, R.string.pk_suggestions).setLoaderManager(
+        //        LoaderManager.getInstance(/* owner= */ this));
     }
 }
diff --git a/src/com/android/car/settings/home/SimpleIconTransitionLineItem.java b/src/com/android/car/settings/home/SimpleIconTransitionLineItem.java
deleted file mode 100644
index 6665d22..0000000
--- a/src/com/android/car/settings/home/SimpleIconTransitionLineItem.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.home;
-
-import android.annotation.DrawableRes;
-import android.annotation.StringRes;
-import android.content.Context;
-import android.view.View;
-
-import com.android.car.list.SimpleIconLineItem;
-import com.android.car.settings.common.BaseFragment;
-
-/**
- * This class extends {@link SimpleIconLineItem} and adds the onClick behavior to
- * trigger the fragmentController to launch a new fragment when clicked.
- * The fragment passed in is what will be launched.
- */
-public class SimpleIconTransitionLineItem extends SimpleIconLineItem {
-
-    private BaseFragment.FragmentController mFragmentController;
-    private BaseFragment mFragment;
-
-    public SimpleIconTransitionLineItem(
-            @StringRes int title,
-            @DrawableRes int iconRes,
-            Context context,
-            CharSequence desc,
-            BaseFragment fragment,
-            BaseFragment.FragmentController fragmentController) {
-        super(title, iconRes, context, desc);
-        mFragment = fragment;
-        mFragmentController = fragmentController;
-    }
-
-    public void onClick(View view) {
-        mFragmentController.launchFragment(mFragment);
-    }
-
-}
diff --git a/src/com/android/car/settings/home/WifiLineItem.java b/src/com/android/car/settings/home/WifiLineItem.java
deleted file mode 100644
index ba2f265..0000000
--- a/src/com/android/car/settings/home/WifiLineItem.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.home;
-
-import android.annotation.DrawableRes;
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.Switch;
-
-import com.android.car.list.IconToggleLineItem;
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.wifi.CarWifiManager;
-import com.android.car.settings.wifi.WifiSettingsFragment;
-import com.android.car.settings.wifi.WifiUtil;
-
-
-/**
- * Represents the wifi line item on settings home page.
- */
-public class WifiLineItem extends IconToggleLineItem {
-    private final Context mContext;
-    private final CarWifiManager mCarWifiManager;
-    private BaseFragment.FragmentController mFragmentController;
-
-    public WifiLineItem(
-            Context context,
-            CarWifiManager carWifiManager,
-            BaseFragment.FragmentController fragmentController) {
-        super(context.getText(R.string.wifi_settings), context);
-        mContext = context;
-        mCarWifiManager = carWifiManager;
-        mFragmentController = fragmentController;
-    }
-
-    @Override
-    public boolean onToggleTouched(Switch toggleSwitch, MotionEvent event) {
-        // intercept touch event, so we can process the request and update the switch
-        // state accordingly
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            mCarWifiManager.setWifiEnabled(!isChecked());
-        }
-        return true;
-    }
-
-    @Override
-    public void onClick(View view) {
-        mFragmentController.launchFragment(WifiSettingsFragment.newInstance());
-    }
-
-    @Override
-    public CharSequence getDesc() {
-        return mContext.getText(R.string.wifi_settings_summary);
-    }
-
-    @Override
-    public boolean isChecked() {
-        return mCarWifiManager.isWifiEnabled();
-    }
-
-    @Override
-    public boolean isExpandable() {
-        return true;
-    }
-
-    @Override
-    public @DrawableRes int getIcon() {
-        return WifiUtil.getIconRes(mCarWifiManager.getWifiState());
-    }
-
-    public void onWifiStateChanged(int state) {
-        if (mIconUpdateListener != null) {
-            mIconUpdateListener.onUpdateIcon(WifiUtil.getIconRes(state));
-        }
-        if (mSwitchStateUpdateListener != null) {
-            mSwitchStateUpdateListener.onToggleChanged(WifiUtil.isWifiOn(state));
-        }
-    }
-
-}
diff --git a/src/com/android/car/settings/inputmethod/EnabledKeyboardPreferenceController.java b/src/com/android/car/settings/inputmethod/EnabledKeyboardPreferenceController.java
new file mode 100644
index 0000000..8c48a05
--- /dev/null
+++ b/src/com/android/car/settings/inputmethod/EnabledKeyboardPreferenceController.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/** Updates the enabled keyboard list. */
+public class EnabledKeyboardPreferenceController extends
+        PreferenceController<PreferenceGroup> {
+    private static final Logger LOG = new Logger(EnabledKeyboardPreferenceController.class);
+
+    private final Map<String, Preference> mPreferences = new ArrayMap<>();
+    private final InputMethodManager mInputMethodManager;
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final PackageManager mPackageManager;
+
+    public EnabledKeyboardPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPackageManager = context.getPackageManager();
+        mDevicePolicyManager =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mInputMethodManager =
+                (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        List<Preference> preferencesToDisplay = new ArrayList<>();
+        Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
+        List<String> permittedList = mDevicePolicyManager.getPermittedInputMethodsForCurrentUser();
+        List<InputMethodInfo> inputMethodInfos = mInputMethodManager.getEnabledInputMethodList();
+        int size = (inputMethodInfos == null) ? 0 : inputMethodInfos.size();
+        for (int i = 0; i < size; ++i) {
+            InputMethodInfo inputMethodInfo = inputMethodInfos.get(i);
+            // permittedList is Null means that all input methods are allowed.
+            boolean isAllowedByOrganization = (permittedList == null)
+                    || permittedList.contains(inputMethodInfo.getPackageName());
+            if (!isAllowedByOrganization) {
+                continue;
+            }
+
+            Preference preference = createPreference(inputMethodInfo);
+            if (mPreferences.containsKey(preference.getKey())) {
+                Preference displayedPreference = mPreferences.get(preference.getKey());
+                if (arePreferencesDifferent(displayedPreference, preference)) {
+                    preferencesToDisplay.add(preference);
+                } else {
+                    preferencesToRemove.remove(preference.getKey());
+                }
+            } else {
+                preferencesToDisplay.add(preference);
+            }
+        }
+        updatePreferenceGroup(preferenceGroup, preferencesToDisplay, preferencesToRemove);
+    }
+
+    private void updatePreferenceGroup(
+            PreferenceGroup preferenceGroup, List<Preference> preferencesToDisplay,
+            Set<String> preferencesToRemove) {
+        Collections.sort(preferencesToDisplay, Comparator.comparing(
+                (Preference a) -> a.getTitle().toString())
+                .thenComparing((Preference a) -> a.getSummary().toString()));
+
+        for (String key : preferencesToRemove) {
+            preferenceGroup.removePreference(mPreferences.get(key));
+            mPreferences.remove(key);
+        }
+
+        for (int i = 0; i < preferencesToDisplay.size(); ++i) {
+            Preference pref = preferencesToDisplay.get(i);
+            pref.setOrder(i);
+            mPreferences.put(pref.getKey(), pref);
+            preferenceGroup.addPreference(pref);
+        }
+    }
+
+    /**
+     * Creates a preference.
+     */
+    private Preference createPreference(InputMethodInfo inputMethodInfo) {
+        Preference preference = new Preference(getContext());
+        preference.setKey(String.valueOf(inputMethodInfo.hashCode()));
+        preference.setIcon(InputMethodUtil.getPackageIcon(mPackageManager, inputMethodInfo));
+        preference.setTitle(InputMethodUtil.getPackageLabel(mPackageManager, inputMethodInfo));
+        preference.setSummary(InputMethodUtil.getSummaryString(
+                getContext(), mInputMethodManager, inputMethodInfo));
+        preference.setOnPreferenceClickListener(pref -> {
+            try {
+                Intent intent = new Intent(Intent.ACTION_MAIN);
+                String settingsActivity = inputMethodInfo.getSettingsActivity();
+                intent.setClassName(inputMethodInfo.getPackageName(), settingsActivity);
+                // Invoke a settings activity of an input method.
+                getContext().startActivity(intent);
+            } catch (final ActivityNotFoundException e) {
+                LOG.d("IME's Settings Activity Not Found. " + e);
+            }
+            return true;
+        });
+        return preference;
+    }
+
+    private boolean arePreferencesDifferent(Preference lhs, Preference rhs) {
+        return !Objects.equals(lhs.getTitle(), rhs.getTitle())
+                || !Objects.equals(lhs.getSummary(), rhs.getSummary());
+    }
+}
diff --git a/src/com/android/car/settings/inputmethod/InputMethodUtil.java b/src/com/android/car/settings/inputmethod/InputMethodUtil.java
new file mode 100644
index 0000000..8100b02
--- /dev/null
+++ b/src/com/android/car/settings/inputmethod/InputMethodUtil.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
+
+import java.util.List;
+
+/** Keyboard utility class. */
+public final class InputMethodUtil {
+    /**
+     * Delimiter for Enabled Input Methods' concatenated string.
+     */
+    public static final char INPUT_METHOD_DELIMITER = ':';
+    /**
+     * Splitter for Enabled Input Methods' concatenated string.
+     */
+    public static final TextUtils.SimpleStringSplitter sInputMethodSplitter =
+            new TextUtils.SimpleStringSplitter(INPUT_METHOD_DELIMITER);
+    @VisibleForTesting
+    static final Drawable NO_ICON = new ColorDrawable(Color.TRANSPARENT);
+
+    private InputMethodUtil() {
+    }
+
+    /** Returns package icon. */
+    public static Drawable getPackageIcon(@NonNull PackageManager packageManager,
+            @NonNull InputMethodInfo inputMethodInfo) {
+        Drawable icon;
+        try {
+            icon = packageManager.getApplicationIcon(inputMethodInfo.getPackageName());
+        } catch (NameNotFoundException e) {
+            icon = NO_ICON;
+        }
+
+        return icon;
+    }
+
+    /** Returns package label. */
+    public static String getPackageLabel(@NonNull PackageManager packageManager,
+            @NonNull InputMethodInfo inputMethodInfo) {
+        return inputMethodInfo.loadLabel(packageManager).toString();
+    }
+
+    /** Returns input method summary. */
+    public static String getSummaryString(@NonNull Context context,
+            @NonNull InputMethodManager inputMethodManager,
+            @NonNull InputMethodInfo inputMethodInfo) {
+        List<InputMethodSubtype> subtypes =
+                inputMethodManager.getEnabledInputMethodSubtypeList(
+                        inputMethodInfo, /* allowsImplicitlySelectedSubtypes= */ true);
+        return InputMethodAndSubtypeUtil.getSubtypeLocaleNameListAsSentence(
+                subtypes, context, inputMethodInfo);
+    }
+
+    /**
+     * Check if input method is enabled.
+     *
+     * @return {@code true} if the input method is enabled.
+     */
+    public static boolean isInputMethodEnabled(ContentResolver resolver,
+            InputMethodInfo inputMethodInfo) {
+        sInputMethodSplitter.setString(getEnabledInputMethodsConcatenatedIds(resolver));
+        while (sInputMethodSplitter.hasNext()) {
+            String inputMethodId = sInputMethodSplitter.next();
+            if (inputMethodId.equals(inputMethodInfo.getId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Enable an input method using its InputMethodInfo.
+     */
+    public static void enableInputMethod(ContentResolver resolver,
+            InputMethodInfo inputMethodInfo) {
+        if (isInputMethodEnabled(resolver, inputMethodInfo)) {
+            return;
+        }
+
+        StringBuilder builder = new StringBuilder();
+        builder.append(getEnabledInputMethodsConcatenatedIds(resolver));
+
+        if (!builder.toString().isEmpty()) {
+            builder.append(INPUT_METHOD_DELIMITER);
+        }
+
+        builder.append(inputMethodInfo.getId());
+
+        setEnabledInputMethodsConcatenatedIds(resolver, builder.toString());
+    }
+
+    /**
+     * Disable an input method if its not the default system input method or if there exists another
+     * enabled input method that can also be set as the default system input method.
+     */
+    public static void disableInputMethod(Context context, InputMethodManager inputMethodManager,
+            InputMethodInfo inputMethodInfo) {
+        List<InputMethodInfo> enabledInputMethodInfos = inputMethodManager
+                .getEnabledInputMethodList();
+        StringBuilder builder = new StringBuilder();
+
+        boolean foundAnotherEnabledDefaultInputMethod = false;
+        boolean isSystemDefault = isDefaultInputMethod(context.getContentResolver(),
+                inputMethodInfo);
+        for (InputMethodInfo enabledInputMethodInfo : enabledInputMethodInfos) {
+            if (enabledInputMethodInfo.getId().equals(inputMethodInfo.getId())) {
+                continue;
+            }
+
+            if (builder.length() > 0) {
+                builder.append(INPUT_METHOD_DELIMITER);
+            }
+
+            builder.append(enabledInputMethodInfo.getId());
+
+            if (isSystemDefault && enabledInputMethodInfo.isDefault(context)) {
+                foundAnotherEnabledDefaultInputMethod = true;
+                setDefaultInputMethodId(context.getContentResolver(),
+                        enabledInputMethodInfo.getId());
+            }
+        }
+
+        if (isSystemDefault && !foundAnotherEnabledDefaultInputMethod) {
+            return;
+        }
+
+        setEnabledInputMethodsConcatenatedIds(context.getContentResolver(), builder.toString());
+    }
+
+    private static String getEnabledInputMethodsConcatenatedIds(ContentResolver resolver) {
+        return Settings.Secure.getString(resolver, Settings.Secure.ENABLED_INPUT_METHODS);
+    }
+
+    private static String getDefaultInputMethodId(ContentResolver resolver) {
+        return Settings.Secure.getString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD);
+    }
+
+    private static boolean isDefaultInputMethod(ContentResolver resolver,
+            InputMethodInfo inputMethodInfo) {
+        return inputMethodInfo.getId().equals(getDefaultInputMethodId(resolver));
+    }
+
+    private static void setEnabledInputMethodsConcatenatedIds(ContentResolver resolver,
+            String enabledInputMethodIds) {
+        Settings.Secure.putString(resolver, Settings.Secure.ENABLED_INPUT_METHODS,
+                enabledInputMethodIds);
+    }
+
+    private static void setDefaultInputMethodId(ContentResolver resolver,
+            String defaultInputMethodId) {
+        Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
+                defaultInputMethodId);
+    }
+}
diff --git a/src/com/android/car/settings/inputmethod/KeyboardFragment.java b/src/com/android/car/settings/inputmethod/KeyboardFragment.java
new file mode 100644
index 0000000..f46c3e1
--- /dev/null
+++ b/src/com/android/car/settings/inputmethod/KeyboardFragment.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Keyboard settings fragment which lists enabled keyboards and a manage keyboard button. */
+public class KeyboardFragment extends SettingsFragment {
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.keyboard_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/inputmethod/KeyboardManagementFragment.java b/src/com/android/car/settings/inputmethod/KeyboardManagementFragment.java
new file mode 100644
index 0000000..9230365
--- /dev/null
+++ b/src/com/android/car/settings/inputmethod/KeyboardManagementFragment.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Keyboard management screen for all available keyboards management. **/
+public class KeyboardManagementFragment extends SettingsFragment {
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.keyboard_management_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/inputmethod/KeyboardManagementPreferenceController.java b/src/com/android/car/settings/inputmethod/KeyboardManagementPreferenceController.java
new file mode 100644
index 0000000..e643867
--- /dev/null
+++ b/src/com/android/car/settings/inputmethod/KeyboardManagementPreferenceController.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Updates the available keyboard list. */
+public class KeyboardManagementPreferenceController extends
+        PreferenceController<PreferenceGroup> {
+    @VisibleForTesting
+    static final String DIRECT_BOOT_WARN_DIALOG_TAG = "DirectBootWarnDialog";
+    @VisibleForTesting
+    static final String SECURITY_WARN_DIALOG_TAG = "SecurityWarnDialog";
+    private static final String KEY_INPUT_METHOD_INFO = "INPUT_METHOD_INFO";
+    private final InputMethodManager mInputMethodManager;
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final PackageManager mPackageManager;
+    private final ConfirmationDialogFragment.ConfirmListener mDirectBootWarnConfirmListener =
+            args -> {
+                InputMethodInfo inputMethodInfo = args.getParcelable(KEY_INPUT_METHOD_INFO);
+                InputMethodUtil.enableInputMethod(getContext().getContentResolver(),
+                        inputMethodInfo);
+                refreshUi();
+            };
+    private final ConfirmationDialogFragment.RejectListener mRejectListener = args ->
+            refreshUi();
+    private final ConfirmationDialogFragment.ConfirmListener mSecurityWarnDialogConfirmListener =
+            args -> {
+                InputMethodInfo inputMethodInfo = args.getParcelable(KEY_INPUT_METHOD_INFO);
+                // The user confirmed to enable a 3rd party IME, but we might need to prompt if
+                // it's not
+                // Direct Boot aware.
+                if (inputMethodInfo.getServiceInfo().directBootAware) {
+                    InputMethodUtil.enableInputMethod(getContext().getContentResolver(),
+                            inputMethodInfo);
+                    refreshUi();
+                } else {
+                    showDirectBootWarnDialog(inputMethodInfo);
+                }
+            };
+
+    public KeyboardManagementPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPackageManager = context.getPackageManager();
+        mDevicePolicyManager =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mInputMethodManager =
+                (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        super.onCreateInternal();
+
+        ConfirmationDialogFragment dialogFragment = (ConfirmationDialogFragment)
+                getFragmentController().findDialogByTag(DIRECT_BOOT_WARN_DIALOG_TAG);
+        ConfirmationDialogFragment.resetListeners(dialogFragment,
+                mDirectBootWarnConfirmListener,
+                mRejectListener);
+
+        dialogFragment = (ConfirmationDialogFragment) getFragmentController()
+                .findDialogByTag(SECURITY_WARN_DIALOG_TAG);
+        ConfirmationDialogFragment.resetListeners(dialogFragment,
+                mSecurityWarnDialogConfirmListener,
+                mRejectListener);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        List<String> permittedInputMethods = mDevicePolicyManager
+                .getPermittedInputMethodsForCurrentUser();
+        Set<String> permittedInputMethodsSet = permittedInputMethods == null ? null : new HashSet<>(
+                permittedInputMethods);
+
+        preferenceGroup.removeAll();
+
+        List<InputMethodInfo> inputMethodInfos = mInputMethodManager.getInputMethodList();
+        if (inputMethodInfos == null || inputMethodInfos.size() == 0) {
+            return;
+        }
+
+        Collections.sort(inputMethodInfos, Comparator.comparing(
+                (InputMethodInfo a) -> InputMethodUtil.getPackageLabel(mPackageManager, a))
+                .thenComparing((InputMethodInfo a) -> InputMethodUtil.getSummaryString(getContext(),
+                        mInputMethodManager, a)));
+
+        for (InputMethodInfo inputMethodInfo : inputMethodInfos) {
+            if (!isInputMethodAllowedByOrganization(permittedInputMethodsSet, inputMethodInfo)) {
+                continue;
+            }
+
+            Preference preference = createSwitchPreference(inputMethodInfo);
+
+            preference.setEnabled(!isOnlyEnabledDefaultInputMethod(inputMethodInfo));
+
+            preferenceGroup.addPreference(preference);
+        }
+    }
+
+    private boolean isInputMethodAllowedByOrganization(Set<String> permittedList,
+            InputMethodInfo inputMethodInfo) {
+        // permittedList is null means that all input methods are allowed.
+        return (permittedList == null) || permittedList.contains(inputMethodInfo.getPackageName());
+    }
+
+    /**
+     * Check if given input method is the only enabled input method that can be a default system
+     * input method.
+     *
+     * @return {@code true} if input method is the only input method that can be a default system
+     * input method.
+     */
+    private boolean isOnlyEnabledDefaultInputMethod(InputMethodInfo inputMethodInfo) {
+        if (!inputMethodInfo.isDefault(getContext())) {
+            return false;
+        }
+
+        List<InputMethodInfo> inputMethodInfos = mInputMethodManager.getEnabledInputMethodList();
+
+        for (InputMethodInfo imi : inputMethodInfos) {
+            if (!imi.isDefault(getContext())) {
+                continue;
+            }
+
+            if (!imi.getId().equals(inputMethodInfo.getId())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Create a SwitchPreference to enable/disable an input method.
+     *
+     * @return {@code SwitchPreference} which allows a user to enable/disable an input method.
+     */
+    private SwitchPreference createSwitchPreference(InputMethodInfo inputMethodInfo) {
+        SwitchPreference switchPreference = new SwitchPreference(getContext());
+        switchPreference.setKey(String.valueOf(inputMethodInfo.getId()));
+        switchPreference.setIcon(InputMethodUtil.getPackageIcon(mPackageManager, inputMethodInfo));
+        switchPreference.setTitle(InputMethodUtil.getPackageLabel(mPackageManager,
+                inputMethodInfo));
+        switchPreference.setChecked(InputMethodUtil.isInputMethodEnabled(getContext()
+                .getContentResolver(), inputMethodInfo));
+        switchPreference.setSummary(InputMethodUtil.getSummaryString(getContext(),
+                mInputMethodManager, inputMethodInfo));
+
+        switchPreference.setOnPreferenceChangeListener((switchPref, newValue) -> {
+            boolean enable = (boolean) newValue;
+            if (enable) {
+                showSecurityWarnDialog(inputMethodInfo);
+            } else {
+                InputMethodUtil.disableInputMethod(getContext(), mInputMethodManager,
+                        inputMethodInfo);
+                refreshUi();
+            }
+            return false;
+        });
+        return switchPreference;
+    }
+
+    private void showDirectBootWarnDialog(InputMethodInfo inputMethodInfo) {
+        ConfirmationDialogFragment dialog = new ConfirmationDialogFragment.Builder(getContext())
+                .setMessage(getContext().getString(R.string.direct_boot_unaware_dialog_message_car))
+                .setPositiveButton(android.R.string.ok, mDirectBootWarnConfirmListener)
+                .setNegativeButton(android.R.string.cancel, mRejectListener)
+                .addArgumentParcelable(KEY_INPUT_METHOD_INFO, inputMethodInfo)
+                .build();
+
+        getFragmentController().showDialog(dialog, DIRECT_BOOT_WARN_DIALOG_TAG);
+    }
+
+    private void showSecurityWarnDialog(InputMethodInfo inputMethodInfo) {
+        CharSequence label = inputMethodInfo.loadLabel(mPackageManager);
+
+        ConfirmationDialogFragment dialog = new ConfirmationDialogFragment.Builder(getContext())
+                .setTitle(android.R.string.dialog_alert_title)
+                .setMessage(getContext().getString(R.string.ime_security_warning, label))
+                .setPositiveButton(android.R.string.ok, mSecurityWarnDialogConfirmListener)
+                .setNegativeButton(android.R.string.cancel, mRejectListener)
+                .addArgumentParcelable(KEY_INPUT_METHOD_INFO, inputMethodInfo)
+                .build();
+
+        getFragmentController().showDialog(dialog, SECURITY_WARN_DIALOG_TAG);
+    }
+}
diff --git a/src/com/android/car/settings/inputmethod/KeyboardPreferenceController.java b/src/com/android/car/settings/inputmethod/KeyboardPreferenceController.java
new file mode 100644
index 0000000..8c08738
--- /dev/null
+++ b/src/com/android/car/settings/inputmethod/KeyboardPreferenceController.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.icu.text.ListFormatter;
+import android.text.BidiFormatter;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Updates the keyboard settings entry summary with the currently enabled keyboard. */
+public class KeyboardPreferenceController extends PreferenceController<Preference> {
+    private static final String SUMMARY_EMPTY = "";
+
+    private final InputMethodManager mInputMethodManager;
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final PackageManager mPackageManager;
+
+    public KeyboardPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPackageManager = context.getPackageManager();
+        mDevicePolicyManager =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mInputMethodManager =
+                (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        List<InputMethodInfo> inputMethodInfos =
+                mInputMethodManager.getEnabledInputMethodList();
+        if (inputMethodInfos == null) {
+            preference.setSummary(SUMMARY_EMPTY);
+            return;
+        }
+
+        // permittedList == null means all input methods are allowed.
+        List<String> permittedList =
+                mDevicePolicyManager.getPermittedInputMethodsForCurrentUser();
+        List<String> labels = new ArrayList<>();
+
+        for (InputMethodInfo inputMethodInfo : inputMethodInfos) {
+            boolean isAllowedByOrganization = permittedList == null
+                    || permittedList.contains(inputMethodInfo.getPackageName());
+            if (!isAllowedByOrganization) {
+                continue;
+            }
+            labels.add(InputMethodUtil.getPackageLabel(mPackageManager, inputMethodInfo));
+        }
+        if (labels.isEmpty()) {
+            preference.setSummary(SUMMARY_EMPTY);
+            return;
+        }
+
+        BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+
+        List<String> summaries = new ArrayList<>();
+        for (String label : labels) {
+            summaries.add(bidiFormatter.unicodeWrap(label));
+        }
+        preference.setSummary(ListFormatter.getInstance().format(summaries));
+    }
+}
diff --git a/src/com/android/car/settings/language/ChildLocalePickerFragment.java b/src/com/android/car/settings/language/ChildLocalePickerFragment.java
new file mode 100644
index 0000000..b5fc5ba
--- /dev/null
+++ b/src/com/android/car/settings/language/ChildLocalePickerFragment.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.internal.app.LocaleStore;
+
+import java.util.Locale;
+
+/** Secondary screen for language selection, when a language has multiple locales. */
+public class ChildLocalePickerFragment extends SettingsFragment {
+
+    /**
+     * Creates a ChildLocalePickerFragment with the parent locale info included in the arguments.
+     */
+    public static ChildLocalePickerFragment newInstance(LocaleStore.LocaleInfo parentLocaleInfo) {
+        Bundle bundle = new Bundle();
+        bundle.putSerializable(LocaleUtil.LOCALE_BUNDLE_KEY, parentLocaleInfo.getLocale());
+        ChildLocalePickerFragment fragment = new ChildLocalePickerFragment();
+        fragment.setArguments(bundle);
+        return fragment;
+    }
+
+    private LanguageBasePreferenceController.LocaleSelectedListener mLocaleSelectedListener;
+    private LocaleStore.LocaleInfo mParentLocaleInfo;
+
+    /**
+     * Allows the creator of ChildLocalePickerFragment to include a listener for when the child
+     * locale is selected.
+     */
+    public void registerChildLocaleSelectedListener(
+            LanguageBasePreferenceController.LocaleSelectedListener listener) {
+        mLocaleSelectedListener = listener;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        Locale locale = (Locale) getArguments().getSerializable(LocaleUtil.LOCALE_BUNDLE_KEY);
+        mParentLocaleInfo = LocaleStore.getLocaleInfo(locale);
+        use(ChildLocalePickerPreferenceController.class,
+                R.string.pk_child_locale_picker).setParentLocaleInfo(mParentLocaleInfo);
+        use(ChildLocalePickerPreferenceController.class,
+                R.string.pk_child_locale_picker).setLocaleSelectedListener(
+                mLocaleSelectedListener);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        TextView titleView = getActivity().findViewById(R.id.title);
+        titleView.setText(mParentLocaleInfo.getFullNameNative());
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.child_locale_picker_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/language/ChildLocalePickerPreferenceController.java b/src/com/android/car/settings/language/ChildLocalePickerPreferenceController.java
new file mode 100644
index 0000000..55fcfef
--- /dev/null
+++ b/src/com/android/car/settings/language/ChildLocalePickerPreferenceController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.internal.app.LocaleStore;
+
+import java.util.Set;
+
+/** Business logic for handling a secondary page for languages which have multiple locales. */
+public class ChildLocalePickerPreferenceController extends LanguageBasePreferenceController {
+
+    private LocaleStore.LocaleInfo mParentLocaleInfo;
+
+    public ChildLocalePickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /** Set the parent locale info which should be used to provide options in this second screen. */
+    public void setParentLocaleInfo(LocaleStore.LocaleInfo parentLocaleInfo) {
+        mParentLocaleInfo = parentLocaleInfo;
+    }
+
+    @Override
+    protected LocalePreferenceProvider defineLocaleProvider() {
+        Set<LocaleStore.LocaleInfo> mLocaleInfoSet = LocaleStore.getLevelLocales(
+                getContext(),
+                getExclusionSet(),
+                mParentLocaleInfo,
+                /* translatedOnly= */ true);
+
+        return LocalePreferenceProvider.newInstance(getContext(), mLocaleInfoSet,
+                mParentLocaleInfo);
+    }
+}
diff --git a/src/com/android/car/settings/language/LanguageBasePreferenceController.java b/src/com/android/car/settings/language/LanguageBasePreferenceController.java
new file mode 100644
index 0000000..6e626b8
--- /dev/null
+++ b/src/com/android/car/settings/language/LanguageBasePreferenceController.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.internal.app.LocalePicker;
+import com.android.internal.app.LocaleStore;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Common business logic shared between the primary and secondary screens for language selection.
+ */
+public abstract class LanguageBasePreferenceController extends
+        PreferenceController<PreferenceGroup> implements Preference.OnPreferenceClickListener {
+
+    /** Actions that should be taken on selection of the preference. */
+    public interface LocaleSelectedListener {
+        /** Handle selection of locale. */
+        void onLocaleSelected(LocaleStore.LocaleInfo localeInfo);
+    }
+
+    private Set<String> mExclusionSet = new HashSet<>();
+    private LocaleSelectedListener mLocaleSelectedListener;
+
+    public LanguageBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /** Register a listener for when a locale is selected. */
+    public void setLocaleSelectedListener(LocaleSelectedListener listener) {
+        mLocaleSelectedListener = listener;
+    }
+
+    /** Gets the exclusion set. */
+    public Set<String> getExclusionSet() {
+        return mExclusionSet;
+    }
+
+    /** Defines the locale provider that should be used by the given preference controller. */
+    protected abstract LocalePreferenceProvider defineLocaleProvider();
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        // Only populate if the preference group is empty.
+        if (preferenceGroup.getPreferenceCount() == 0) {
+            defineLocaleProvider().populateBasePreference(preferenceGroup, this);
+        }
+    }
+
+    /**
+     * Defines the action that should be taken when a locale with children is clicked. By default,
+     * does nothing.
+     */
+    protected void handleLocaleWithChildren(LocaleStore.LocaleInfo parentLocaleInfo) {
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        LocaleStore.LocaleInfo localeInfo = LocaleUtil.getLocaleArgument(preference);
+
+        // Preferences without a associated locale should not be acted on.
+        if (localeInfo == null) {
+            return false;
+        }
+
+        if (localeInfo.getParent() == null) {
+            // The locale only has the language info. Need to look up the sub-level
+            // locale to get the country/region info as well.
+            Set<LocaleStore.LocaleInfo> subLocales = LocaleStore.getLevelLocales(
+                    getContext(),
+                    getExclusionSet(),
+                    /* parent */ localeInfo,
+                    /* translatedOnly */ true);
+
+            if (subLocales.size() > 1) {
+                handleLocaleWithChildren(localeInfo);
+                return true;
+            }
+
+            if (subLocales.size() < 1) {
+                return false;
+            }
+
+            // If only 1 sublocale, just operate as if there are no sublocales.
+            localeInfo = subLocales.iterator().next();
+        }
+
+        LocalePicker.updateLocale(localeInfo.getLocale());
+        if (mLocaleSelectedListener != null) {
+            mLocaleSelectedListener.onLocaleSelected(localeInfo);
+        }
+        getFragmentController().goBack();
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/language/LanguagePickerFragment.java b/src/com/android/car/settings/language/LanguagePickerFragment.java
new file mode 100644
index 0000000..9d3fd54
--- /dev/null
+++ b/src/com/android/car/settings/language/LanguagePickerFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Fragment for showing the list of languages. */
+public class LanguagePickerFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.language_picker_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/language/LanguagePickerPreferenceController.java b/src/com/android/car/settings/language/LanguagePickerPreferenceController.java
new file mode 100644
index 0000000..fd6d9d4
--- /dev/null
+++ b/src/com/android/car/settings/language/LanguagePickerPreferenceController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Build;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.internal.app.LocaleStore;
+
+import java.util.Locale;
+import java.util.Set;
+
+/** Business logic for showing and acting on languages in the language settings screen. */
+public class LanguagePickerPreferenceController extends LanguageBasePreferenceController {
+
+    public LanguagePickerPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected LocalePreferenceProvider defineLocaleProvider() {
+        Set<LocaleStore.LocaleInfo> localeInfoSet = LocaleStore.getLevelLocales(
+                getContext(),
+                getExclusionSet(),
+                /* parent= */ null,
+                /* translatedOnly= */ true);
+        maybeAddPseudoLocale(localeInfoSet);
+
+        return LocalePreferenceProvider.newInstance(getContext(), localeInfoSet,
+                /* parentLocale= */ null);
+    }
+
+    @Override
+    protected void handleLocaleWithChildren(LocaleStore.LocaleInfo parentLocaleInfo) {
+        ChildLocalePickerFragment fragment = ChildLocalePickerFragment.newInstance(
+                parentLocaleInfo);
+        fragment.registerChildLocaleSelectedListener(
+                localeInfo -> getFragmentController().goBack());
+        getFragmentController().launchFragment(fragment);
+    }
+
+    /**
+     * Add a pseudo locale in debug build for testing RTL.
+     *
+     * @param localeInfos the set of {@link LocaleStore.LocaleInfo} to which the locale is added.
+     */
+    private void maybeAddPseudoLocale(Set<LocaleStore.LocaleInfo> localeInfos) {
+        if (Build.IS_USERDEBUG) {
+            // The ar-XB pseudo-locale is RTL.
+            localeInfos.add(LocaleStore.getLocaleInfo(new Locale("ar", "XB")));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/language/LanguageSettingsEntryPreferenceController.java b/src/com/android/car/settings/language/LanguageSettingsEntryPreferenceController.java
new file mode 100644
index 0000000..a363ad9
--- /dev/null
+++ b/src/com/android/car/settings/language/LanguageSettingsEntryPreferenceController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settingslib.language.LanguagePickerUtils;
+import com.android.internal.app.LocaleHelper;
+
+import java.util.Locale;
+
+/** Updates the language settings entry summary with the currently configured locale. */
+public class LanguageSettingsEntryPreferenceController extends PreferenceController<Preference> {
+
+    public LanguageSettingsEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        Locale locale = LanguagePickerUtils.getConfiguredLocale();
+        preference.setSummary(
+                LocaleHelper.getDisplayName(locale, locale, /* sentenceCase= */ true));
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+}
diff --git a/src/com/android/car/settings/language/LanguagesAndInputFragment.java b/src/com/android/car/settings/language/LanguagesAndInputFragment.java
new file mode 100644
index 0000000..d815da0
--- /dev/null
+++ b/src/com/android/car/settings/language/LanguagesAndInputFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Groups together settings related to languages and input. */
+public class LanguagesAndInputFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.languages_and_input_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/language/LocalePreferenceProvider.java b/src/com/android/car/settings/language/LocalePreferenceProvider.java
new file mode 100644
index 0000000..7fd21aa
--- /dev/null
+++ b/src/com/android/car/settings/language/LocalePreferenceProvider.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceUtil;
+import com.android.car.settingslib.R;
+import com.android.car.settingslib.language.LanguagePickerUtils;
+import com.android.internal.app.LocaleStore;
+import com.android.internal.app.SuggestedLocaleAdapter;
+
+import java.util.Set;
+
+/**
+ * Provides a wrapper around the {@link SuggestedLocaleAdapter} to create Preferences to populate
+ * the Language Settings screen.
+ */
+public class LocalePreferenceProvider {
+
+    private static final Logger LOG = new Logger(LanguagePickerPreferenceController.class);
+
+    /** Creates a new instance of the preference provider. */
+    public static LocalePreferenceProvider newInstance(Context context,
+            Set<LocaleStore.LocaleInfo> localeInfoSet,
+            @Nullable LocaleStore.LocaleInfo parentLocale) {
+        SuggestedLocaleAdapter adapter = LanguagePickerUtils.createSuggestedLocaleAdapter(context,
+                localeInfoSet, parentLocale);
+        return new LocalePreferenceProvider(context, adapter);
+    }
+
+    /**
+     * Header types are copied from {@link SuggestedLocaleAdapter} in order to be able to
+     * determine the header rows.
+     */
+    @VisibleForTesting
+    static final int TYPE_HEADER_SUGGESTED = 0;
+    @VisibleForTesting
+    static final int TYPE_HEADER_ALL_OTHERS = 1;
+    @VisibleForTesting
+    static final int TYPE_LOCALE = 2;
+
+    private final Context mContext;
+    private SuggestedLocaleAdapter mSuggestedLocaleAdapter;
+
+    @VisibleForTesting
+    LocalePreferenceProvider(Context context, SuggestedLocaleAdapter localeAdapter) {
+        mContext = context;
+        mSuggestedLocaleAdapter = localeAdapter;
+    }
+
+    /**
+     * Populates the base preference group based on the hierarchy provided by this provider.
+     *
+     * @param base     the preference container which will hold the language preferences created by
+     *                 this provider
+     * @param listener the click listener registered to the language/locale preferences contained in
+     *                 the base preference group
+     */
+    public void populateBasePreference(PreferenceGroup base,
+            Preference.OnPreferenceClickListener listener) {
+        /*
+         * LocalePreferenceProvider can give elements to be represented in 2 ways. In the first
+         * way, it simply provides the LocalePreferences which lists the available options. In the
+         * second way, this provider may also provide PreferenceCategories to break up the
+         * options into "Suggested" and "All others". The screen is constructed by taking a look
+         * at the type of Preference that is provided through LocalePreferenceProvider.
+         *
+         * In the first case (no subcategories), preferences are added directly to the base
+         * container. Otherwise, elements are added to the last category that was provided
+         * (stored in "category").
+         */
+        PreferenceCategory category = null;
+        for (int position = 0; position < mSuggestedLocaleAdapter.getCount(); position++) {
+            Preference preference = getPreference(position);
+            if (PreferenceUtil.checkPreferenceType(preference, PreferenceCategory.class)) {
+                category = (PreferenceCategory) preference;
+                base.addPreference(category);
+            } else {
+                preference.setOnPreferenceClickListener(listener);
+                if (category == null) {
+                    base.addPreference(preference);
+                } else {
+                    category.addPreference(preference);
+                }
+            }
+        }
+    }
+
+    /**
+     * Constructs a PreferenceCategory or Preference with locale arguments based on the type of item
+     * provided.
+     */
+    private Preference getPreference(int position) {
+        int type = mSuggestedLocaleAdapter.getItemViewType(position);
+        switch (type) {
+            case TYPE_HEADER_SUGGESTED:
+            case TYPE_HEADER_ALL_OTHERS:
+                PreferenceCategory category = new PreferenceCategory(mContext);
+                category.setTitle(type == TYPE_HEADER_SUGGESTED
+                        ? R.string.language_picker_list_suggested_header
+                        : R.string.language_picker_list_all_header);
+                return category;
+            case TYPE_LOCALE:
+                LocaleStore.LocaleInfo info =
+                        (LocaleStore.LocaleInfo) mSuggestedLocaleAdapter.getItem(position);
+                Preference preference = new Preference(mContext);
+                preference.setTitle(info.getFullNameNative());
+                LocaleUtil.setLocaleArgument(preference, info);
+                return preference;
+            default:
+                LOG.d("Attempting to get unknown type: " + type);
+                throw new IllegalStateException("Unknown locale list item type");
+        }
+    }
+}
diff --git a/src/com/android/car/settings/language/LocaleUtil.java b/src/com/android/car/settings/language/LocaleUtil.java
new file mode 100644
index 0000000..dc29b54
--- /dev/null
+++ b/src/com/android/car/settings/language/LocaleUtil.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.internal.app.LocaleStore;
+
+import java.util.Locale;
+
+/** Utilities to get/set LocaleInfo from preferences. */
+public class LocaleUtil {
+    /** This key is also used to add Locale to the bundle for {@link ChildLocalePickerFragment}. */
+    public static final String LOCALE_BUNDLE_KEY = "locale_key";
+
+    /** Private constructor to prevent others from instantiating this class. */
+    private LocaleUtil() {
+    }
+
+    /** Extract the locale from the given locale info and add to preference arguments. */
+    public static void setLocaleArgument(Preference preference, LocaleStore.LocaleInfo localeInfo) {
+        preference.getExtras().putSerializable(LOCALE_BUNDLE_KEY, localeInfo.getLocale());
+    }
+
+    /** Extract the localeInfo from the preference, if it exists. */
+    @Nullable
+    public static LocaleStore.LocaleInfo getLocaleArgument(Preference preference) {
+        Locale locale = (Locale) preference.getExtras().getSerializable(LOCALE_BUNDLE_KEY);
+        if (locale == null) {
+            return null;
+        }
+        return LocaleStore.getLocaleInfo(locale);
+    }
+}
diff --git a/src/com/android/car/settings/location/BluetoothScanningPreferenceController.java b/src/com/android/car/settings/location/BluetoothScanningPreferenceController.java
new file mode 100644
index 0000000..1427bfd
--- /dev/null
+++ b/src/com/android/car/settings/location/BluetoothScanningPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Handles Bluetooth location scanning settings.
+ */
+public class BluetoothScanningPreferenceController extends
+        PreferenceController<TwoStatePreference> {
+
+    public BluetoothScanningPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH_LE) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean isBluetoothScanningEnabled = (boolean) newValue;
+        Settings.Global.putInt(getContext().getContentResolver(),
+                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, isBluetoothScanningEnabled ? 1 : 0);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/location/LocationFooterPreferenceController.java b/src/com/android/car/settings/location/LocationFooterPreferenceController.java
new file mode 100644
index 0000000..d2a1631
--- /dev/null
+++ b/src/com/android/car/settings/location/LocationFooterPreferenceController.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.location.LocationManager;
+
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Injects Location Footers into a {@link PreferenceGroup} with a matching key.
+ */
+public class LocationFooterPreferenceController extends PreferenceController<PreferenceGroup> {
+    private static final Logger LOG = new Logger(LocationFooterPreferenceController.class);
+    private static final Intent INJECT_INTENT =
+            new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION);
+
+    private List<LocationFooter> mLocationFooters;
+    // List of Location Footer Injectors that will be used to broadcast a
+    // LocationManager.SETTINGS_FOOTER_REMOVED_ACTION intent on controller stop.
+    private final List<ComponentName> mFooterInjectors = new ArrayList<>();
+    private PackageManager mPackageManager;
+
+    public LocationFooterPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPackageManager = context.getPackageManager();
+    }
+
+    @VisibleForTesting
+    void setPackageManager(PackageManager packageManager) {
+        mPackageManager = packageManager;
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mLocationFooters = getInjectedLocationFooters();
+        for (LocationFooter footer : mLocationFooters) {
+            String footerString;
+            try {
+                footerString = mPackageManager
+                        .getResourcesForApplication(footer.mApplicationInfo)
+                        .getString(footer.mFooterStringRes);
+            } catch (PackageManager.NameNotFoundException exception) {
+                LOG.w("Resources not found for application "
+                        + footer.mApplicationInfo.packageName);
+                continue;
+            }
+
+            // For each injected footer: Create a new preference, set the summary
+            // and icon, then inject under the footer preference group.
+            Preference newPreference = new Preference(getContext());
+            newPreference.setSummary(footerString);
+            newPreference.setIcon(R.drawable.ic_settings_about);
+            getPreference().addPreference(newPreference);
+
+            // Send broadcast to the injector announcing a footer has been injected
+            sendBroadcast(footer.mComponentName,
+                    LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION);
+            // Add the component to the list of injectors so that
+            // it receives a broadcast when the footer is removed.
+            mFooterInjectors.add(footer.mComponentName);
+        }
+    }
+
+    /**
+     * Send a {@link LocationManager#SETTINGS_FOOTER_REMOVED_ACTION} broadcast to footer injectors
+     * when LocationSettingsFragment is stopped.
+     */
+    @Override
+    protected void onStopInternal() {
+        // Send broadcast to the footer injectors. Notify them the footer is not visible.
+        for (ComponentName componentName : mFooterInjectors) {
+            sendBroadcast(componentName, LocationManager.SETTINGS_FOOTER_REMOVED_ACTION);
+        }
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mLocationFooters = null;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0);
+    }
+
+    /**
+     * Return a list of strings provided by ACTION_INJECT_FOOTER broadcast receivers. If there
+     * are no injectors, an immutable emptry list is returned.
+     */
+    private List<LocationFooter> getInjectedLocationFooters() {
+        List<ResolveInfo> resolveInfos = mPackageManager.queryBroadcastReceivers(
+                INJECT_INTENT, PackageManager.GET_META_DATA);
+        if (resolveInfos == null) {
+            LOG.e("Unable to resolve intent " + INJECT_INTENT);
+            return Collections.emptyList();
+        } else {
+            LOG.d("Found broadcast receivers: " + resolveInfos);
+        }
+
+        List<LocationFooter> locationFooters = new ArrayList<>(resolveInfos.size());
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ActivityInfo activityInfo = resolveInfo.activityInfo;
+            ApplicationInfo appInfo = activityInfo.applicationInfo;
+
+            // If a non-system app tries to inject footer, ignore it
+            if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                LOG.w("Ignoring attempt to inject footer from a non-system app: " + resolveInfo);
+                continue;
+            }
+
+            // If the injector does not have valid METADATA, ignore it
+            if (activityInfo.metaData == null) {
+                LOG.d("No METADATA in broadcast receiver " + activityInfo.name);
+                continue;
+            }
+
+            // Get the footer text resource id from broadcast receiver's metadata
+            int footerTextRes =
+                    activityInfo.metaData.getInt(LocationManager.METADATA_SETTINGS_FOOTER_STRING);
+            if (footerTextRes == 0) {
+                LOG.w("No mapping of integer exists for "
+                        + LocationManager.METADATA_SETTINGS_FOOTER_STRING);
+                continue;
+            }
+            locationFooters.add(new LocationFooter(footerTextRes, appInfo,
+                    new ComponentName(activityInfo.packageName, activityInfo.name)));
+        }
+        return locationFooters;
+    }
+
+    private void sendBroadcast(ComponentName componentName, String action) {
+        Intent intent = new Intent(action);
+        intent.setComponent(componentName);
+        getContext().sendBroadcast(intent);
+    }
+
+    /**
+     * Contains information related to a footer.
+     */
+    private static class LocationFooter {
+        // The string resource of the footer.
+        @StringRes
+        private final int mFooterStringRes;
+        // Application info of the receiver injecting this footer.
+        private final ApplicationInfo mApplicationInfo;
+        // The component that injected the footer. It must be a receiver of
+        // LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION broadcast.
+        private final ComponentName mComponentName;
+
+        LocationFooter(@StringRes int footerRes, ApplicationInfo appInfo,
+                ComponentName componentName) {
+            mFooterStringRes = footerRes;
+            mApplicationInfo = appInfo;
+            mComponentName = componentName;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/location/LocationScanningFragment.java b/src/com/android/car/settings/location/LocationScanningFragment.java
new file mode 100644
index 0000000..7b7fa5d
--- /dev/null
+++ b/src/com/android/car/settings/location/LocationScanningFragment.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Fragment that hosts Location Scanning settings.
+ */
+public class LocationScanningFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.location_scanning_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/location/LocationScanningPreferenceController.java b/src/com/android/car/settings/location/LocationScanningPreferenceController.java
new file mode 100644
index 0000000..9b7a259
--- /dev/null
+++ b/src/com/android/car/settings/location/LocationScanningPreferenceController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Hides the Scanning entry if neither Wi-Fi nor Bluetooth are supported.
+ */
+public class LocationScanningPreferenceController extends PreferenceController<Preference> {
+
+    public LocationScanningPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        PackageManager packageManager = getContext().getPackageManager();
+        return (packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)
+                || packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))
+                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+}
diff --git a/src/com/android/car/settings/location/LocationServicesPreferenceController.java b/src/com/android/car/settings/location/LocationServicesPreferenceController.java
new file mode 100644
index 0000000..f0b4ff9
--- /dev/null
+++ b/src/com/android/car/settings/location/LocationServicesPreferenceController.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.SettingInjectorService;
+import android.os.UserHandle;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.location.SettingsInjector;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Injects Location Services into a {@link PreferenceGroup} with a matching key.
+ */
+public class LocationServicesPreferenceController extends PreferenceController<PreferenceGroup> {
+    private static final Logger LOG = new Logger(LocationServicesPreferenceController.class);
+    private static final IntentFilter INTENT_FILTER_INJECTED_SETTING_CHANGED = new IntentFilter(
+            SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED);
+    private SettingsInjector mSettingsInjector;
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            LOG.i("Received injected settings change intent: " + intent);
+            mSettingsInjector.reloadStatusMessages();
+        }
+    };
+
+    public LocationServicesPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mSettingsInjector = new SettingsInjector(context);
+    }
+
+    @VisibleForTesting
+    void setSettingsInjector(SettingsInjector injector) {
+        mSettingsInjector = injector;
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        int profileId = UserHandle.USER_CURRENT;
+        if (mSettingsInjector.hasInjectedSettings(profileId)) {
+            // If there are injected settings, get and inject them.
+            List<Preference> injectedSettings = getSortedInjectedPreferences(profileId);
+            for (Preference preference : injectedSettings) {
+                getPreference().addPreference(preference);
+            }
+        }
+    }
+
+    /**
+     * Called when the controller is started.
+     */
+    @Override
+    protected void onStartInternal() {
+        getContext().registerReceiver(mReceiver, INTENT_FILTER_INJECTED_SETTING_CHANGED);
+    }
+
+    /**
+     * Called when the controller is stopped.
+     */
+    @Override
+    protected void onStopInternal() {
+        getContext().unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+
+        preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0);
+    }
+
+    private List<Preference> getSortedInjectedPreferences(int profileId) {
+        List<Preference> sortedInjections = new ArrayList<>(
+                mSettingsInjector.getInjectedSettings(getContext(), profileId));
+        Collections.sort(sortedInjections);
+        return sortedInjections;
+    }
+}
diff --git a/src/com/android/car/settings/location/LocationSettingsActivity.java b/src/com/android/car/settings/location/LocationSettingsActivity.java
new file mode 100644
index 0000000..732ab29
--- /dev/null
+++ b/src/com/android/car/settings/location/LocationSettingsActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.location;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.common.BaseCarSettingsActivity;
+
+/**
+ * Starts {@link LocationSettingsFragment} in a separate activity to help with back navigation flow.
+ */
+public class LocationSettingsActivity extends BaseCarSettingsActivity {
+
+    @Nullable
+    @Override
+    protected Fragment getInitialFragment() {
+        return new LocationSettingsFragment();
+    }
+}
diff --git a/src/com/android/car/settings/location/LocationSettingsFragment.java b/src/com/android/car/settings/location/LocationSettingsFragment.java
new file mode 100644
index 0000000..91591e9
--- /dev/null
+++ b/src/com/android/car/settings/location/LocationSettingsFragment.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.widget.Switch;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.Utils;
+
+/**
+ * Main page that hosts Location related preferences.
+ */
+public class LocationSettingsFragment extends SettingsFragment {
+    private static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED =
+            new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
+
+    private LocationManager mLocationManager;
+    private Switch mLocationSwitch;
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mLocationSwitch.setChecked(mLocationManager.isLocationEnabled());
+        }
+    };
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.location_settings_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_toggle;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mLocationManager = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mLocationSwitch = requireActivity().findViewById(R.id.toggle_switch);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        requireContext().registerReceiver(mReceiver, INTENT_FILTER_LOCATION_MODE_CHANGED);
+        updateLocationSwitch();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        requireContext().unregisterReceiver(mReceiver);
+    }
+
+    // Update the location master switch's state upon starting the fragment.
+    private void updateLocationSwitch() {
+        mLocationSwitch.setChecked(mLocationManager.isLocationEnabled());
+        mLocationSwitch.setOnCheckedChangeListener((buttonView, isChecked) ->
+                Utils.updateLocationEnabled(requireContext(), isChecked, UserHandle.myUserId(),
+                        Settings.Secure.LOCATION_CHANGER_SYSTEM_SETTINGS));
+    }
+}
diff --git a/src/com/android/car/settings/location/RecentLocationRequestsEntryPreferenceController.java b/src/com/android/car/settings/location/RecentLocationRequestsEntryPreferenceController.java
new file mode 100644
index 0000000..882517c
--- /dev/null
+++ b/src/com/android/car/settings/location/RecentLocationRequestsEntryPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import android.app.Service;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.LocationManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Disables Recent Location Requests entry when location is off.
+ */
+public class RecentLocationRequestsEntryPreferenceController extends
+        PreferenceController<Preference> {
+
+    private static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED =
+            new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
+
+    private final LocationManager mLocationManager;
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshUi();
+        }
+    };
+
+    public RecentLocationRequestsEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mLocationManager = (LocationManager) getContext().getSystemService(
+                Service.LOCATION_SERVICE);
+    }
+
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setEnabled(mLocationManager.isLocationEnabled());
+    }
+
+    @Override
+    protected void onStartInternal() {
+        getContext().registerReceiver(mReceiver, INTENT_FILTER_LOCATION_MODE_CHANGED);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        getContext().unregisterReceiver(mReceiver);
+    }
+}
diff --git a/src/com/android/car/settings/location/RecentLocationRequestsFragment.java b/src/com/android/car/settings/location/RecentLocationRequestsFragment.java
new file mode 100644
index 0000000..592225a
--- /dev/null
+++ b/src/com/android/car/settings/location/RecentLocationRequestsFragment.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Shows a list of applications that have requested location recently.
+ */
+public class RecentLocationRequestsFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.location_recent_requests_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/location/RecentLocationRequestsPreferenceController.java b/src/com/android/car/settings/location/RecentLocationRequestsPreferenceController.java
new file mode 100644
index 0000000..bdc1ea5
--- /dev/null
+++ b/src/com/android/car/settings/location/RecentLocationRequestsPreferenceController.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.applications.ApplicationDetailsFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.location.RecentLocationApps;
+import com.android.settingslib.location.RecentLocationApps.Request;
+
+import java.util.List;
+
+/**
+ * Displays all apps that have requested location recently.
+ */
+public class RecentLocationRequestsPreferenceController extends
+        PreferenceController<PreferenceGroup> {
+    private RecentLocationApps mRecentLocationApps;
+    // This list will always be sorted by most recent first.
+    private List<Request> mRecentLocationRequests;
+
+    public RecentLocationRequestsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mRecentLocationApps = new RecentLocationApps(context);
+    }
+
+    @VisibleForTesting
+    void setRecentLocationApps(RecentLocationApps apps) {
+        mRecentLocationApps = apps;
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup group) {
+        if (mRecentLocationRequests == null) {
+            // First time displaying a list.
+            mRecentLocationRequests = mRecentLocationApps.getAppListSorted();
+        } else {
+            // If preferences were already added to the screen, get a new list
+            // and only update the displayed app-list if there is a difference.
+            List<Request> newRequests = mRecentLocationApps.getAppListSorted();
+            // listsEqual compares by elements' package names only, using List.equals() will
+            // not work because it will always return false since it also compares the time.
+            if (listsEqual(newRequests, mRecentLocationRequests)) {
+                return;
+            }
+            mRecentLocationRequests = newRequests;
+        }
+        if (mRecentLocationRequests.isEmpty()) {
+            Preference emptyMessagePref = new Preference(getContext());
+            emptyMessagePref.setTitle(R.string.location_settings_recent_requests_empty_message);
+            group.addPreference(emptyMessagePref);
+        } else {
+            group.removeAll();
+            for (Request request : mRecentLocationRequests) {
+                Preference appPref = createPreference(request);
+                group.addPreference(appPref);
+            }
+        }
+    }
+
+    private Preference createPreference(Request request) {
+        Preference pref = new Preference(getContext());
+        pref.setSummary(request.contentDescription);
+        pref.setIcon(request.icon);
+        pref.setTitle(request.label);
+        Intent intent = new Intent();
+        intent.setPackage(request.packageName);
+        ResolveInfo resolveInfo = getContext().getPackageManager().resolveActivity(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        pref.setOnPreferenceClickListener(p -> {
+            getFragmentController().launchFragment(
+                    ApplicationDetailsFragment.getInstance(resolveInfo.activityInfo.packageName));
+            return true;
+        });
+        return pref;
+    }
+
+    /**
+     * Compares two {@link Request} lists by the elements' package names.
+     *
+     * @param a The first list.
+     * @param b The second list.
+     * @return {@code true} if both lists have the same elements (by package name) and order.
+     */
+    private boolean listsEqual(List<Request> a, List<Request> b) {
+        if (a.size() != b.size()) {
+            return false;
+        }
+        for (int i = 0; i < a.size(); i++) {
+            if (!a.get(i).packageName.equals(b.get(i).packageName)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/location/WifiScanningPreferenceController.java b/src/com/android/car/settings/location/WifiScanningPreferenceController.java
new file mode 100644
index 0000000..6a8031a
--- /dev/null
+++ b/src/com/android/car/settings/location/WifiScanningPreferenceController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Handles Wi-Fi location scanning settings.
+ */
+public class WifiScanningPreferenceController extends PreferenceController<TwoStatePreference> {
+
+    public WifiScanningPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)
+                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean isWifiScanningEnabled = (boolean) newValue;
+        Settings.Global.putInt(getContext().getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, isWifiScanningEnabled ? 1 : 0);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/network/ConfirmMobileDataDisableDialog.java b/src/com/android/car/settings/network/ConfirmMobileDataDisableDialog.java
new file mode 100644
index 0000000..f0898f8
--- /dev/null
+++ b/src/com/android/car/settings/network/ConfirmMobileDataDisableDialog.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+
+import com.android.car.settings.R;
+
+/** Dialog to confirm disabling mobile data. */
+public class ConfirmMobileDataDisableDialog extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    /**
+     * Tag used to open and identify the dialog fragment from the FragmentManager or
+     * FragmentController.
+     */
+    public static final String TAG = "ConfirmMobileDataDisableDialog";
+
+    private ConfirmMobileDataDisableListener mListener;
+
+    /**
+     * Sets a listener which will determine how to handle the user action when they press ok or
+     * cancel.
+     */
+    public void setListener(ConfirmMobileDataDisableListener listener) {
+        mListener = listener;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getContext())
+                .setMessage(R.string.confirm_mobile_data_disable)
+                .setPositiveButton(android.R.string.ok, this)
+                .setNegativeButton(android.R.string.cancel, this)
+                .create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            if (mListener != null) {
+                mListener.onMobileDataDisableConfirmed();
+            }
+        }
+        if (which == DialogInterface.BUTTON_NEGATIVE) {
+            if (mListener != null) {
+                mListener.onMobileDataDisableRejected();
+            }
+        }
+    }
+
+    /**
+     * Interface for listeners that want to receive a callback when user confirms to disable mobile
+     * data.
+     */
+    public interface ConfirmMobileDataDisableListener {
+        /**
+         * Method called only when user presses confirm button.
+         */
+        void onMobileDataDisableConfirmed();
+
+        /**
+         * Method called only when user presses cancel button.
+         */
+        void onMobileDataDisableRejected();
+    }
+}
diff --git a/src/com/android/car/settings/network/MobileDataTogglePreferenceController.java b/src/com/android/car/settings/network/MobileDataTogglePreferenceController.java
new file mode 100644
index 0000000..ebb9f36
--- /dev/null
+++ b/src/com/android/car/settings/network/MobileDataTogglePreferenceController.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Business logic to control the toggle that enables/disables usage of mobile data. Does not have
+ * support for multi-sim.
+ */
+public class MobileDataTogglePreferenceController extends
+        PreferenceController<TwoStatePreference> implements
+        ConfirmMobileDataDisableDialog.ConfirmMobileDataDisableListener {
+
+    private TelephonyManager mTelephonyManager;
+
+    public MobileDataTogglePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        ConfirmMobileDataDisableDialog dialog =
+                (ConfirmMobileDataDisableDialog) getFragmentController().findDialogByTag(
+                        ConfirmMobileDataDisableDialog.TAG);
+        if (dialog != null) {
+            dialog.setListener(this);
+        }
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(mTelephonyManager.isDataEnabled());
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean newToggleValue = (Boolean) newValue;
+        if (!newToggleValue) {
+            ConfirmMobileDataDisableDialog dialog = new ConfirmMobileDataDisableDialog();
+            dialog.setListener(this);
+            getFragmentController().showDialog(dialog, ConfirmMobileDataDisableDialog.TAG);
+        } else {
+            setMobileDataEnabled(true);
+        }
+        return false;
+    }
+
+    @Override
+    public void onMobileDataDisableConfirmed() {
+        setMobileDataEnabled(false);
+    }
+
+    @Override
+    public void onMobileDataDisableRejected() {
+        refreshUi();
+    }
+
+    private void setMobileDataEnabled(boolean enabled) {
+        mTelephonyManager.setDataEnabled(enabled);
+        refreshUi();
+    }
+}
diff --git a/src/com/android/car/settings/network/MobileNetworkEntryPreferenceController.java b/src/com/android/car/settings/network/MobileNetworkEntryPreferenceController.java
new file mode 100644
index 0000000..3db2065
--- /dev/null
+++ b/src/com/android/car/settings/network/MobileNetworkEntryPreferenceController.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Controls the preference for accessing mobile network settings. */
+public class MobileNetworkEntryPreferenceController extends PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final TelephonyManager mTelephonyManager;
+    private final ConnectivityManager mConnectivityManager;
+    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onServiceStateChanged(ServiceState serviceState) {
+            refreshUi();
+        }
+    };
+    private final BroadcastReceiver mAirplaneModeChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshUi();
+        }
+    };
+    private final IntentFilter mIntentFilter = new IntentFilter(
+            Intent.ACTION_AIRPLANE_MODE_CHANGED);
+
+    public MobileNetworkEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+        getContext().registerReceiver(mAirplaneModeChangedReceiver, mIntentFilter);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+        getContext().unregisterReceiver(mAirplaneModeChangedReceiver);
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (!NetworkUtils.hasMobileNetwork(mConnectivityManager)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
+        boolean isNotAdmin = !mCarUserManagerHelper.getCurrentProcessUserInfo().isAdmin();
+        boolean hasRestriction = mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+        if (isNotAdmin || hasRestriction) {
+            return DISABLED_FOR_USER;
+        }
+        return AVAILABLE;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(mTelephonyManager.getNetworkOperatorName());
+        preference.setEnabled(isAirplaneModeOff());
+    }
+
+    private boolean isAirplaneModeOff() {
+        return Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) == 0;
+    }
+}
diff --git a/src/com/android/car/settings/network/MobileNetworkFragment.java b/src/com/android/car/settings/network/MobileNetworkFragment.java
new file mode 100644
index 0000000..27cb83e
--- /dev/null
+++ b/src/com/android/car/settings/network/MobileNetworkFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Mobile network settings homepage. */
+public class MobileNetworkFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.mobile_network_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/network/NetworkAndInternetFragment.java b/src/com/android/car/settings/network/NetworkAndInternetFragment.java
new file mode 100644
index 0000000..8dc5e8a
--- /dev/null
+++ b/src/com/android/car/settings/network/NetworkAndInternetFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 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.car.settings.network;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Fragment for all wifi/mobile data connectivity preferences. */
+public class NetworkAndInternetFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.network_and_internet_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/network/NetworkUtils.java b/src/com/android/car/settings/network/NetworkUtils.java
new file mode 100644
index 0000000..fe3fc05
--- /dev/null
+++ b/src/com/android/car/settings/network/NetworkUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.telephony.TelephonyManager;
+
+/** Provides helpful utilities surrounding network related tasks. */
+public final class NetworkUtils {
+
+    private NetworkUtils() {
+    }
+
+    /** Returns {@code true} if device has a mobile network. */
+    public static boolean hasMobileNetwork(ConnectivityManager connectivityManager) {
+        Network[] networks = connectivityManager.getAllNetworks();
+        for (Network network : networks) {
+            NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
+            if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Returns {@code true} if device has a sim card. */
+    public static boolean hasSim(TelephonyManager telephonyManager) {
+        int simState = telephonyManager.getSimState();
+
+        // Note that pulling out the SIM card returns UNKNOWN, not ABSENT.
+        return simState != TelephonyManager.SIM_STATE_ABSENT
+                && simState != TelephonyManager.SIM_STATE_UNKNOWN;
+    }
+}
diff --git a/src/com/android/car/settings/quicksettings/BluetoothTile.java b/src/com/android/car/settings/quicksettings/BluetoothTile.java
index d82fe10..63caccf 100644
--- a/src/com/android/car/settings/quicksettings/BluetoothTile.java
+++ b/src/com/android/car/settings/quicksettings/BluetoothTile.java
@@ -11,13 +11,11 @@
  * 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
+ * limitations under the License.
  */
+
 package com.android.car.settings.quicksettings;
 
-
-import android.annotation.DrawableRes;
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -26,7 +24,12 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+
 import com.android.car.settings.R;
+import com.android.car.settings.bluetooth.BluetoothSettingsFragment;
+import com.android.car.settings.common.FragmentController;
 import com.android.car.settings.common.Logger;
 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -40,6 +43,7 @@
     private final StateChangedListener mStateChangedListener;
     private LocalBluetoothAdapter mLocalAdapter;
     private LocalBluetoothManager mLocalManager;
+    private View.OnLongClickListener mLaunchBluetoothSettings;
 
     @DrawableRes
     private int mIconRes = R.drawable.ic_settings_bluetooth;
@@ -65,7 +69,7 @@
                         break;
                     default:
                         mIconRes = R.drawable.ic_settings_bluetooth;
-                        mText = mContext.getString(R.string.bluetooth_settings);
+                        mText = mContext.getString(R.string.bluetooth_settings_title);
                         mState = State.ON;
                 }
             } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
@@ -84,7 +88,10 @@
         }
     };
 
-    BluetoothTile(Context context, StateChangedListener stateChangedListener) {
+    BluetoothTile(
+            Context context,
+            StateChangedListener stateChangedListener,
+            FragmentController fragmentController) {
         mStateChangedListener = stateChangedListener;
         mContext = context;
         IntentFilter mBtStateChangeFilter = new IntentFilter();
@@ -97,7 +104,7 @@
             LOG.e("Bluetooth is not supported on this device");
             return;
         }
-        mText = mContext.getString(R.string.bluetooth_settings);
+        mText = mContext.getString(R.string.bluetooth_settings_title);
         mLocalAdapter = mLocalManager.getBluetoothAdapter();
         if (mLocalAdapter.isEnabled()) {
             mIconRes = R.drawable.ic_settings_bluetooth;
@@ -106,11 +113,15 @@
             mIconRes = R.drawable.ic_settings_bluetooth_disabled;
             mState = State.OFF;
         }
+        mLaunchBluetoothSettings = v -> {
+            fragmentController.launchFragment(new BluetoothSettingsFragment());
+            return true;
+        };
     }
 
     @Nullable
-    public View.OnClickListener getDeepDiveListener() {
-        return null;
+    public View.OnLongClickListener getOnLongClickListener() {
+        return mLaunchBluetoothSettings;
     }
 
     @Override
@@ -145,6 +156,6 @@
         if (mLocalAdapter == null) {
             return;
         }
-        mLocalAdapter.setBluetoothEnabled(mLocalAdapter.isEnabled() ? false : true);
+        mLocalAdapter.setBluetoothEnabled(!mLocalAdapter.isEnabled());
     }
 }
diff --git a/src/com/android/car/settings/quicksettings/BrightnessTile.java b/src/com/android/car/settings/quicksettings/BrightnessTile.java
index c98c70f..592e138 100644
--- a/src/com/android/car/settings/quicksettings/BrightnessTile.java
+++ b/src/com/android/car/settings/quicksettings/BrightnessTile.java
@@ -17,8 +17,15 @@
 
 import static android.provider.Settings.System.SCREEN_BRIGHTNESS;
 
+import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX;
+import static com.android.settingslib.display.BrightnessUtils.convertGammaToLinear;
+import static com.android.settingslib.display.BrightnessUtils.convertLinearToGamma;
+
+import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
-import android.provider.Settings;
+import android.os.PowerManager;
+import android.provider.Settings.SettingNotFoundException;
+import android.provider.Settings.System;
 import android.widget.SeekBar;
 
 import com.android.car.settings.common.Logger;
@@ -28,11 +35,17 @@
  */
 public class BrightnessTile implements QuickSettingGridAdapter.SeekbarTile {
     private static final Logger LOG = new Logger(BrightnessTile.class);
-    private static final int MAX_BRIGHTNESS = 255;
+    private CarUserManagerHelper mCarUserManagerHelper;
     private final Context mContext;
+    private final int mMaximumBacklight;
+    private final int mMinimumBacklight;
 
     public BrightnessTile(Context context) {
         mContext = context;
+        mCarUserManagerHelper = new CarUserManagerHelper(mContext);
+        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mMaximumBacklight = powerManager.getMaximumScreenBrightnessSetting();
+        mMinimumBacklight = powerManager.getMinimumScreenBrightnessSetting();
     }
 
     @Override
@@ -46,13 +59,15 @@
     }
 
     @Override
-    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-        Settings.System.putInt(mContext.getContentResolver(), SCREEN_BRIGHTNESS, progress);
+    public void onProgressChanged(SeekBar seekBar, int gamma, boolean fromUser) {
+        int linear = convertGammaToLinear(gamma, mMinimumBacklight, mMaximumBacklight);
+        System.putIntForUser(mContext.getContentResolver(), SCREEN_BRIGHTNESS, linear,
+                             mCarUserManagerHelper.getCurrentForegroundUserId());
     }
 
     @Override
     public int getMax() {
-        return MAX_BRIGHTNESS;
+        return GAMMA_SPACE_MAX;
     }
 
     @Override
@@ -62,13 +77,14 @@
 
     @Override
     public int getCurrent() {
-        int currentBrightness = 0;
+        int gamma = GAMMA_SPACE_MAX;
         try {
-            currentBrightness = Settings.System.getInt(mContext.getContentResolver(),
-                    SCREEN_BRIGHTNESS);
-        } catch (Settings.SettingNotFoundException e) {
+            int linear = System.getIntForUser(mContext.getContentResolver(), SCREEN_BRIGHTNESS,
+                                              mCarUserManagerHelper.getCurrentForegroundUserId());
+            gamma = convertLinearToGamma(linear, mMinimumBacklight, mMaximumBacklight);
+        } catch (SettingNotFoundException e) {
             LOG.w("Can't find setting for SCREEN_BRIGHTNESS.");
         }
-        return currentBrightness;
+        return gamma;
     }
 }
diff --git a/src/com/android/car/settings/quicksettings/CelluarTile.java b/src/com/android/car/settings/quicksettings/CelluarTile.java
index e1cafba..8471e1e 100644
--- a/src/com/android/car/settings/quicksettings/CelluarTile.java
+++ b/src/com/android/car/settings/quicksettings/CelluarTile.java
@@ -13,15 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.car.settings.quicksettings;
 
-
-import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.telephony.TelephonyManager;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.car.settings.R;
 import com.android.settingslib.net.DataUsageController;
 
@@ -56,7 +57,7 @@
     }
 
     @Nullable
-    public View.OnClickListener getDeepDiveListener() {
+    public View.OnLongClickListener getOnLongClickListener() {
         return null;
     }
 
diff --git a/src/com/android/car/settings/quicksettings/DayNightTile.java b/src/com/android/car/settings/quicksettings/DayNightTile.java
index e4c599c..5f0df2c 100644
--- a/src/com/android/car/settings/quicksettings/DayNightTile.java
+++ b/src/com/android/car/settings/quicksettings/DayNightTile.java
@@ -13,26 +13,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.car.settings.quicksettings;
 
-
-import android.annotation.DrawableRes;
-import android.annotation.Nullable;
 import android.app.UiModeManager;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.view.View;
-import android.view.View.OnClickListener;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
 
 import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.display.DisplaySettingsFragment;
 
 /**
- * Controls Day night mode tile on quick setting page.
+ * Toggles auto or night mode tile on quick setting page.
  */
 public class DayNightTile implements QuickSettingGridAdapter.Tile {
     private final Context mContext;
     private final StateChangedListener mStateChangedListener;
     private final UiModeManager mUiModeManager;
+    private final View.OnLongClickListener mLaunchDisplaySettings;
 
     @DrawableRes
     private int mIconRes = R.drawable.ic_settings_night_display;
@@ -41,7 +44,10 @@
 
     private State mState = State.ON;
 
-    DayNightTile(Context context, StateChangedListener stateChangedListener) {
+    DayNightTile(
+            Context context,
+            StateChangedListener stateChangedListener,
+            FragmentController fragmentController) {
         mStateChangedListener = stateChangedListener;
         mContext = context;
         mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
@@ -51,11 +57,15 @@
             mState = State.OFF;
         }
         mText = mContext.getString(R.string.night_mode_tile_label);
+        mLaunchDisplaySettings = v -> {
+            fragmentController.launchFragment(new DisplaySettingsFragment());
+            return true;
+        };
     }
 
     @Nullable
-    public OnClickListener getDeepDiveListener() {
-        return null;
+    public View.OnLongClickListener getOnLongClickListener() {
+        return mLaunchDisplaySettings;
     }
 
     @Override
@@ -86,7 +96,7 @@
     @Override
     public void onClick(View v) {
         if (mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES) {
-            mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+            mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_AUTO);
         } else {
             mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_YES);
         }
diff --git a/src/com/android/car/settings/quicksettings/QuickSettingFragment.java b/src/com/android/car/settings/quicksettings/QuickSettingFragment.java
index 9e177c6..5a091ef 100644
--- a/src/com/android/car/settings/quicksettings/QuickSettingFragment.java
+++ b/src/com/android/car/settings/quicksettings/QuickSettingFragment.java
@@ -13,18 +13,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.car.settings.quicksettings;
 
+import android.app.Activity;
 import android.car.drivingstate.CarUxRestrictions;
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
 import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemProperties;
+import android.os.UserManager;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.widget.ImageView;
+import android.widget.Button;
 import android.widget.TextView;
 
-import androidx.car.widget.PagedListView;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
@@ -33,115 +42,170 @@
 import com.android.car.settings.users.UserIconProvider;
 import com.android.car.settings.users.UserSwitcherFragment;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Shows a page to access frequently used settings.
  */
 public class QuickSettingFragment extends BaseFragment {
-    private CarUserManagerHelper  mCarUserManagerHelper;
+    // Time to delay refreshing the build info, if the clock is not correct.
+    private static final long BUILD_INFO_REFRESH_TIME_MS = TimeUnit.SECONDS.toMillis(5);
+
+    private CarUserManagerHelper mCarUserManagerHelper;
     private UserIconProvider mUserIconProvider;
     private QuickSettingGridAdapter mGridAdapter;
-    private PagedListView mListView;
-    private View mFullSettingBtn;
+    private RecyclerView mListView;
+    private View mFullSettingsBtn;
     private View mUserSwitcherBtn;
     private HomeFragmentLauncher mHomeFragmentLauncher;
     private float mOpacityDisabled;
     private float mOpacityEnabled;
+    private TextView mBuildInfo;
 
-    /**
-     * Returns an instance of this class.
-     */
-    public static QuickSettingFragment newInstance() {
-        QuickSettingFragment quickSettingFragment = new QuickSettingFragment();
-        Bundle bundle = quickSettingFragment.getBundle();
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_quick_settings);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.quick_settings);
-        bundle.putInt(EXTRA_TITLE_ID, R.string.settings_label);
-        quickSettingFragment.setArguments(bundle);
-        return quickSettingFragment;
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_quick_settings;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return R.layout.quick_settings;
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
         mHomeFragmentLauncher = new HomeFragmentLauncher();
-        getActivity().findViewById(R.id.action_bar_icon_container).setOnClickListener(
-                v -> getActivity().finish());
+        Activity activity = requireActivity();
+        activity.findViewById(R.id.action_bar_icon_container).setOnClickListener(
+                v -> activity.finish());
 
-        mOpacityDisabled = getContext().getResources().getFloat(R.dimen.opacity_disabled);
-        mOpacityEnabled = getContext().getResources().getFloat(R.dimen.opacity_enabled);
-        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
+        mOpacityDisabled = activity.getResources().getFloat(R.dimen.opacity_disabled);
+        mOpacityEnabled = activity.getResources().getFloat(R.dimen.opacity_enabled);
+        mCarUserManagerHelper = new CarUserManagerHelper(activity);
         mUserIconProvider = new UserIconProvider(mCarUserManagerHelper);
-        mListView = (PagedListView) getActivity().findViewById(R.id.list);
-        mGridAdapter = new QuickSettingGridAdapter(getContext());
-        mListView.getRecyclerView().setLayoutManager(mGridAdapter.getGridLayoutManager());
+        mListView = activity.findViewById(R.id.list);
+        mGridAdapter = new QuickSettingGridAdapter(activity);
+        mListView.setLayoutManager(mGridAdapter.getGridLayoutManager());
 
-        mFullSettingBtn = getActivity().findViewById(R.id.full_setting_btn);
-        mFullSettingBtn.setOnClickListener(mHomeFragmentLauncher);
-        mUserSwitcherBtn = getActivity().findViewById(R.id.user_switcher_btn);
+        mFullSettingsBtn = activity.findViewById(R.id.full_settings_btn);
+        mFullSettingsBtn.setOnClickListener(mHomeFragmentLauncher);
+        mUserSwitcherBtn = activity.findViewById(R.id.user_switcher_btn);
         mUserSwitcherBtn.setOnClickListener(v -> {
-            getFragmentController().launchFragment(UserSwitcherFragment.newInstance());
+            getFragmentController().launchFragment(new UserSwitcherFragment());
         });
+        setupUserButton(activity);
 
-        View exitBtn = getActivity().findViewById(R.id.exit_button);
+        View exitBtn = activity.findViewById(R.id.action_bar_icon_container);
         exitBtn.setOnClickListener(v -> getFragmentController().goBack());
 
         mGridAdapter
-                .addTile(new WifiTile(getContext(), mGridAdapter, getFragmentController()))
-                .addTile(new BluetoothTile(getContext(), mGridAdapter))
-                .addTile(new DayNightTile(getContext(), mGridAdapter))
-                .addTile(new CelluarTile(getContext(), mGridAdapter))
-                .addSeekbarTile(new BrightnessTile(getContext()));
+                .addTile(new WifiTile(activity, mGridAdapter, getFragmentController()))
+                .addTile(new BluetoothTile(activity, mGridAdapter, getFragmentController()))
+                .addTile(new DayNightTile(activity, mGridAdapter, getFragmentController()))
+                .addTile(new CelluarTile(activity, mGridAdapter))
+                .addSeekbarTile(new BrightnessTile(activity));
         mListView.setAdapter(mGridAdapter);
     }
 
     @Override
+    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        mBuildInfo = view.requireViewById(R.id.build_info);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        mUserSwitcherBtn.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
+        // In non-user builds (that is, user-debug, eng, etc), display some version information.
+        if (!Build.IS_USER) {
+            refreshBuildInfo();
+        }
+    }
+
+    private void refreshBuildInfo() {
+        if (!isAdded()) {
+            // This can happen if the delayed post happens before we're stopped. Just give up
+            // trying to get the right clock.
+            return;
+        }
+
+        long buildTimeDiffDays =
+                TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - Build.TIME);
+        if (buildTimeDiffDays < 0) {
+            // If it's in the past, that likely means the current time is wrong (or the build time
+            // could be wrong, but that's less likely). Reschedule this to run in a few seconds to
+            // see whether the clock's been fixed.
+            mBuildInfo.postDelayed(this::refreshBuildInfo, BUILD_INFO_REFRESH_TIME_MS);
+        }
+
+        String str = String.format(getResources().getString(R.string.build_info_fmt),
+                Build.FINGERPRINT, SystemProperties.get("ro.build.date", "<unknown>"),
+                buildTimeDiffDays < 0 ? "--" : Long.toString(buildTimeDiffDays));
+
+        mBuildInfo.setVisibility(View.VISIBLE);
+        mBuildInfo.setText(str);
+    }
+
+    @Override
     public void onStop() {
         super.onStop();
         mGridAdapter.stop();
     }
 
-    private void setupAccountButton() {
-        ImageView userIcon = (ImageView) getActivity().findViewById(R.id.user_icon);
+    private void setupUserButton(Context context) {
+        Button userButton = requireActivity().findViewById(R.id.user_switcher_btn);
         UserInfo currentUserInfo = mCarUserManagerHelper.getCurrentForegroundUserInfo();
-        userIcon.setImageDrawable(mUserIconProvider.getUserIcon(currentUserInfo, getContext()));
-        userIcon.clearColorFilter();
+        Drawable userIcon = mUserIconProvider.getUserIcon(currentUserInfo, context);
+        userButton.setCompoundDrawablesRelativeWithIntrinsicBounds(userIcon, /* top= */
+                null, /* end= */ null, /* bottom= */ null);
+        userButton.setText(currentUserInfo.name);
+    }
 
-        TextView userSwitcherText = (TextView) getActivity().findViewById(R.id.user_switcher_text);
-        userSwitcherText.setText(currentUserInfo.name);
+    private boolean showUserSwitcher() {
+        return !UserManager.isDeviceInDemoMode(getContext())
+                && UserManager.supportsMultipleUsers()
+                && !UserManager.get(getContext()).hasUserRestriction(
+                UserManager.DISALLOW_USER_SWITCH);
     }
 
     /**
      * Quick setting fragment is distraction optimized, so is allowed at all times.
      */
     @Override
-    public boolean canBeShown(CarUxRestrictions carUxRestrictions) {
+    public boolean canBeShown(@NonNull CarUxRestrictions carUxRestrictions) {
         return true;
     }
 
     @Override
-    public void onUxRestrictionChanged(CarUxRestrictions carUxRestrictions) {
+    public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
         // TODO: update tiles
-        applyRestriction(CarUxRestrictionsHelper.isNoSetup(carUxRestrictions));
+        applyRestriction(CarUxRestrictionsHelper.isNoSetup(restrictionInfo));
     }
 
     private void applyRestriction(boolean restricted) {
-        mHomeFragmentLauncher.showDOBlockingMessage(restricted);
-        mFullSettingBtn.setAlpha(restricted ? mOpacityDisabled : mOpacityEnabled);
+        mHomeFragmentLauncher.showBlockingMessage(restricted);
+        mFullSettingsBtn.setAlpha(restricted ? mOpacityDisabled : mOpacityEnabled);
     }
 
     private class HomeFragmentLauncher implements OnClickListener {
-        private boolean mShowDOBlockingMessage;
+        private boolean mShowBlockingMessage;
 
-        private void showDOBlockingMessage(boolean show) {
-            mShowDOBlockingMessage = show;
+        private void showBlockingMessage(boolean show) {
+            mShowBlockingMessage = show;
         }
 
         @Override
         public void onClick(View v) {
-            if (mShowDOBlockingMessage) {
-                getFragmentController().showDOBlockingMessage();
+            if (mShowBlockingMessage) {
+                getFragmentController().showBlockingMessage();
             } else {
-                getFragmentController().launchFragment(HomepageFragment.newInstance());
+                getFragmentController().launchFragment(new HomepageFragment());
             }
         }
     }
diff --git a/src/com/android/car/settings/quicksettings/QuickSettingGridAdapter.java b/src/com/android/car/settings/quicksettings/QuickSettingGridAdapter.java
index cc83e61..89b0232 100644
--- a/src/com/android/car/settings/quicksettings/QuickSettingGridAdapter.java
+++ b/src/com/android/car/settings/quicksettings/QuickSettingGridAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -13,22 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
+
 package com.android.car.settings.quicksettings;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.SeekBar;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.car.settings.R;
 
 import java.util.ArrayList;
@@ -70,7 +71,9 @@
          * A state to indicate how we want to render icon, this is independent of what to show
          * in text.
          */
-        enum State {OFF, ON}
+        enum State {
+            OFF, ON
+        }
 
         /**
          * Called when activity owning this tile's onStop() gets called.
@@ -90,11 +93,11 @@
         boolean isAvailable();
 
         /**
-         * Returns a listener for launching a setting fragment for advanced configuration for this
-         * tile, A.K.A deep dive, if available. {@code null} if deep dive is not available.
+         * Returns a listener to call when this tile is clicked and held. Returns {@code null} if
+         * no action should be performed.
          */
         @Nullable
-        OnClickListener getDeepDiveListener();
+        View.OnLongClickListener getOnLongClickListener();
     }
 
     interface SeekbarTile extends SeekBar.OnSeekBarChangeListener {
@@ -158,15 +161,12 @@
             case TILE_VIEWTYPE:
                 Tile tile = mTiles.get(position - mSeekbarTiles.size());
                 TileViewHolder vh = (TileViewHolder) holder;
-                OnClickListener deepDiveListener = tile.getDeepDiveListener();
                 vh.itemView.setOnClickListener(tile);
-                if (deepDiveListener != null) {
-                    vh.mDeepDiveIcon.setVisibility(View.VISIBLE);
-                    vh.mTextContainer.setOnClickListener(deepDiveListener);
+                View.OnLongClickListener onLongClickListener = tile.getOnLongClickListener();
+                if (onLongClickListener != null) {
+                    vh.itemView.setOnLongClickListener(onLongClickListener);
                 } else {
-                    vh.mDeepDiveIcon.setVisibility(View.GONE);
-                    vh.mTextContainer.setClickable(false);
-                    vh.mTextContainer.setOnClickListener(null);
+                    vh.itemView.setOnLongClickListener(null);
                 }
                 vh.mIcon.setImageDrawable(tile.getIcon());
                 switch (tile.getState()) {
@@ -199,28 +199,22 @@
     }
 
     private class TileViewHolder extends RecyclerView.ViewHolder {
-        private final View mIconContainer;
-        private final View mTextContainer;
         private final View mIconBackground;
         private final ImageView mIcon;
-        private final ImageView mDeepDiveIcon;
         private final TextView mText;
 
         TileViewHolder(View view) {
             super(view);
-            mIconContainer = view.findViewById(R.id.icon_container);
-            mTextContainer = view.findViewById(R.id.text_container);
             mIconBackground = view.findViewById(R.id.icon_background);
-            mIcon = (ImageView) view.findViewById(R.id.tile_icon);
-            mDeepDiveIcon = (ImageView) view.findViewById(R.id.deep_dive_icon);
-            mText = (TextView) view.findViewById(R.id.tile_text);
+            mIcon = view.findViewById(R.id.tile_icon);
+            mText = view.findViewById(R.id.tile_text);
         }
     }
 
     class QsSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
 
         /**
-         * Each line item takes a full row, and each tile takes only 1 span.
+         * Each list item takes a full row, and each tile takes only 1 span.
          */
         @Override
         public int getSpanSize(int position) {
diff --git a/src/com/android/car/settings/quicksettings/WifiTile.java b/src/com/android/car/settings/quicksettings/WifiTile.java
index 592f255..a6272f4 100644
--- a/src/com/android/car/settings/quicksettings/WifiTile.java
+++ b/src/com/android/car/settings/quicksettings/WifiTile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -13,18 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
+
 package com.android.car.settings.quicksettings;
 
-
-import android.annotation.DrawableRes;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.view.View;
-import android.view.View.OnClickListener;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment.FragmentController;
+import com.android.car.settings.common.FragmentController;
 import com.android.car.settings.wifi.CarWifiManager;
 import com.android.car.settings.wifi.WifiSettingsFragment;
 import com.android.car.settings.wifi.WifiUtil;
@@ -37,9 +37,8 @@
     private final StateChangedListener mStateChangedListener;
     private final CarWifiManager mCarWifiManager;
     private final Context mContext;
-    private final FragmentController mFragmentController;
 
-    private final OnClickListener mSwitchSavedWifiListener;
+    private final View.OnLongClickListener mLaunchWifiSettings;
 
     @DrawableRes
     private int mIconRes = R.drawable.ic_settings_wifi;
@@ -48,15 +47,17 @@
 
     private State mState = State.OFF;
 
-    WifiTile(Context context, StateChangedListener stateChangedListener,
+    WifiTile(
+            Context context,
+            StateChangedListener stateChangedListener,
             FragmentController fragmentController) {
         mContext = context;
-        mFragmentController = fragmentController;
-        mSwitchSavedWifiListener = v -> {
-            mFragmentController.launchFragment(
-                    WifiSettingsFragment.newInstance().showSavedApOnly(true));
+        mLaunchWifiSettings = v -> {
+            fragmentController.launchFragment(new WifiSettingsFragment());
+            return true;
         };
-        mCarWifiManager = new CarWifiManager(context, /* listener= */ this);
+        mCarWifiManager = new CarWifiManager(context);
+        mCarWifiManager.addListener(this);
         mCarWifiManager.start();
         mStateChangedListener = stateChangedListener;
         // init icon and text etc.
@@ -65,9 +66,8 @@
     }
 
     @Nullable
-    public OnClickListener getDeepDiveListener() {
-        return !mCarWifiManager.getSavedAccessPoints().isEmpty()
-                ? mSwitchSavedWifiListener : null;
+    public View.OnLongClickListener getOnLongClickListener() {
+        return mLaunchWifiSettings;
     }
 
     @Override
@@ -93,6 +93,7 @@
 
     @Override
     public void stop() {
+        mCarWifiManager.removeListener(this);
         mCarWifiManager.stop();
         mCarWifiManager.destroy();
     }
@@ -130,6 +131,7 @@
 
     /**
      * Updates the text with access point connected, if any
+     *
      * @return {@code true} if the text is updated, {@code false} other wise.
      */
     private boolean updateAccessPointSsid() {
diff --git a/src/com/android/car/settings/security/CheckLockActivity.java b/src/com/android/car/settings/security/CheckLockActivity.java
new file mode 100644
index 0000000..85a58c3
--- /dev/null
+++ b/src/com/android/car/settings/security/CheckLockActivity.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.common.BaseCarSettingsActivity;
+import com.android.car.settings.common.Logger;
+import com.android.internal.widget.LockPatternUtils;
+
+/**
+ * Prompts the user to enter their pin, password, or pattern lock (if set) and returns
+ * {@link #RESULT_OK} on a successful entry or immediately if the user has no lock setup.
+ */
+public class CheckLockActivity extends BaseCarSettingsActivity implements CheckLockListener {
+
+    private static final Logger LOG = new Logger(CheckLockActivity.class);
+
+    @Override
+    @Nullable
+    protected Fragment getInitialFragment() {
+        Fragment fragment;
+        int passwordQuality = new LockPatternUtils(this).getKeyguardStoredPasswordQuality(
+                UserHandle.myUserId());
+        switch (passwordQuality) {
+            case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
+                // User has not set a password.
+                setResult(RESULT_OK);
+                finish();
+                return null;
+            case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+                fragment = new ConfirmLockPatternFragment();
+                break;
+            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+                fragment = ConfirmLockPinPasswordFragment.newPinInstance();
+                break;
+            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+                fragment = ConfirmLockPinPasswordFragment.newPasswordInstance();
+                break;
+            default:
+                LOG.e("Unexpected password quality: " + String.valueOf(passwordQuality));
+                fragment = ConfirmLockPinPasswordFragment.newPasswordInstance();
+        }
+
+        Bundle bundle = fragment.getArguments();
+        if (bundle == null) {
+            bundle = new Bundle();
+        }
+        bundle.putInt(ChooseLockTypeFragment.EXTRA_CURRENT_PASSWORD_QUALITY, passwordQuality);
+        fragment.setArguments(bundle);
+        return fragment;
+    }
+
+    @Override
+    public void onLockVerified(byte[] lock) {
+        setResult(RESULT_OK);
+        finish();
+    }
+}
diff --git a/src/com/android/car/settings/security/CheckLockListener.java b/src/com/android/car/settings/security/CheckLockListener.java
index cde5e35..45289e3 100644
--- a/src/com/android/car/settings/security/CheckLockListener.java
+++ b/src/com/android/car/settings/security/CheckLockListener.java
@@ -25,5 +25,5 @@
      *
      * @param lock The verified credential
      */
-    void onLockVerified(String lock);
+    void onLockVerified(byte[] lock);
 }
diff --git a/src/com/android/car/settings/security/CheckLockWorker.java b/src/com/android/car/settings/security/CheckLockWorker.java
index bd3483b..856218f 100644
--- a/src/com/android/car/settings/security/CheckLockWorker.java
+++ b/src/com/android/car/settings/security/CheckLockWorker.java
@@ -17,7 +17,8 @@
 package com.android.car.settings.security;
 
 import android.os.Bundle;
-import android.support.v4.app.Fragment;
+
+import androidx.fragment.app.Fragment;
 
 import com.android.car.settings.common.Logger;
 import com.android.internal.widget.LockPatternChecker;
@@ -93,7 +94,7 @@
      * Checks lock PIN/password asynchronously.  To receive callback when check is completed,
      * implement {@link Listener} and call {@link #setListener(Listener)}.
      */
-    public final void checkPinPassword(int userId, String password) {
+    public final void checkPinPassword(int userId, byte[] password) {
         if (mCheckInProgress) {
             LOG.w("Check pin/password request issued while one is still running");
             return;
diff --git a/src/com/android/car/settings/security/ChooseLockPatternFragment.java b/src/com/android/car/settings/security/ChooseLockPatternFragment.java
index 2fee8aa..0e954ac 100644
--- a/src/com/android/car/settings/security/ChooseLockPatternFragment.java
+++ b/src/com/android/car/settings/security/ChooseLockPatternFragment.java
@@ -16,14 +16,17 @@
 
 package com.android.car.settings.security;
 
-import android.annotation.StringRes;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.support.annotation.VisibleForTesting;
 import android.view.View;
 import android.widget.Button;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
 import com.android.car.settings.common.Logger;
@@ -35,6 +38,7 @@
 import com.google.android.collect.Lists;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -44,178 +48,119 @@
 public class ChooseLockPatternFragment extends BaseFragment {
 
     private static final Logger LOG = new Logger(ChooseLockPatternFragment.class);
-    private static final String LOCK_OPTIONS_DIALOG_TAG = "lock_options_dialog_tag";
     private static final String FRAGMENT_TAG_SAVE_PATTERN_WORKER = "save_pattern_worker";
+    private static final String STATE_UI_STAGE = "state_ui_stage";
+    private static final String STATE_CHOSEN_PATTERN = "state_chosen_pattern";
     private static final int ID_EMPTY_MESSAGE = -1;
-
+    /**
+     * The patten used during the help screen to show how to draw a pattern.
+     */
+    private final List<LockPatternView.Cell> mAnimatePattern =
+            Collections.unmodifiableList(Lists.newArrayList(
+                    LockPatternView.Cell.of(0, 0),
+                    LockPatternView.Cell.of(0, 1),
+                    LockPatternView.Cell.of(1, 1),
+                    LockPatternView.Cell.of(2, 1)
+            ));
     // How long we wait to clear a wrong pattern
     private int mWrongPatternClearTimeOut;
     private int mUserId;
-    private boolean mIsInSetupWizard;
-
     private Stage mUiStage = Stage.Introduction;
     private LockPatternView mLockPatternView;
     private TextView mMessageText;
     private Button mSecondaryButton;
     private Button mPrimaryButton;
+    private ProgressBar mProgressBar;
     private List<LockPatternView.Cell> mChosenPattern;
-    private String mCurrentPattern;
+    // Existing pattern that user previously set
+    private byte[] mCurrentPattern;
     private SavePatternWorker mSavePatternWorker;
+    private Runnable mClearPatternRunnable = () -> mLockPatternView.clearPattern();
+    // The pattern listener that responds according to a user choosing a new
+    // lock pattern.
+    private final LockPatternView.OnPatternListener mChooseNewLockPatternListener =
+            new LockPatternView.OnPatternListener() {
+                @Override
+                public void onPatternStart() {
+                    mLockPatternView.removeCallbacks(mClearPatternRunnable);
+                    updateUIWhenPatternInProgress();
+                }
 
-    /**
-     * Keep track internally of where the user is in choosing a pattern.
-     */
-    enum Stage {
-        /**
-         * Initial stage when first launching choose a lock pattern.
-         * Pattern mEnabled, secondary button allow for Cancel, primary button disabled.
-         */
-        Introduction(
-                R.string.lockpattern_recording_intro_header,
-                SecondaryButtonState.Cancel, PrimaryButtonState.ContinueDisabled,
-                /* patternEnabled= */ true),
-        /**
-         * Help screen to show how a valid pattern looks like.
-         * Pattern disabled, primary button shows Ok. No secondary button.
-         */
-        HelpScreen(
-                R.string.lockpattern_settings_help_how_to_record,
-                SecondaryButtonState.Gone, PrimaryButtonState.Ok,
-                /* patternEnabled= */ false),
-        /**
-         * Invalid pattern is entered, hint message show required number of dots.
-         * Secondary button allows for Retry, primary button disabled.
-         */
-        ChoiceTooShort(
-                R.string.lockpattern_recording_incorrect_too_short,
-                SecondaryButtonState.Retry, PrimaryButtonState.ContinueDisabled,
-                /* patternEnabled= */ true),
-        /**
-         * First drawing on the pattern is valid, primary button shows Continue,
-         * can proceed to next screen.
-         */
-        FirstChoiceValid(
-                R.string.lockpattern_recording_intro_header,
-                SecondaryButtonState.Retry, PrimaryButtonState.Continue,
-                /* patternEnabled= */ false),
-        /**
-         * Need to draw pattern again to confirm.
-         * Secondary button allows for Cancel, primary button disabled.
-         */
-        NeedToConfirm(
-                R.string.lockpattern_need_to_confirm,
-                SecondaryButtonState.Cancel, PrimaryButtonState.ConfirmDisabled,
-                /* patternEnabled= */ true),
-        /**
-         * Confirmation of previous drawn pattern failed, didn't enter the same pattern.
-         * Need to re-draw the pattern to match the fist pattern.
-         */
-        ConfirmWrong(
-                R.string.lockpattern_pattern_wrong,
-                SecondaryButtonState.Cancel, PrimaryButtonState.ConfirmDisabled,
-                /* patternEnabled= */ true),
-        /**
-         * Pattern is confirmed after drawing the same pattern twice.
-         * Pattern disabled.
-         */
-        ChoiceConfirmed(
-                R.string.lockpattern_pattern_confirmed,
-                SecondaryButtonState.Cancel, PrimaryButtonState.Confirm,
-                /* patternEnabled= */ false),
+                @Override
+                public void onPatternCleared() {
+                    mLockPatternView.removeCallbacks(mClearPatternRunnable);
+                }
 
-        /**
-         * Error saving pattern.
-         * Pattern disabled, primary button shows Retry, secondary button allows for cancel
-         */
-        SaveFailure(
-                R.string.error_saving_lockpattern,
-                SecondaryButtonState.Cancel, PrimaryButtonState.Retry,
-                /* patternEnabled= */ false);
+                @Override
+                public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+                    switch (mUiStage) {
+                        case Introduction:
+                        case ChoiceTooShort:
+                            handlePatternEntered(pattern);
+                            break;
+                        case ConfirmWrong:
+                        case NeedToConfirm:
+                            handleConfirmPattern(pattern);
+                            break;
+                        default:
+                            throw new IllegalStateException("Unexpected stage " + mUiStage
+                                    + " when entering the pattern.");
+                    }
+                }
 
-        final int mMessageId;
-        final SecondaryButtonState mSecondaryButtonState;
-        final PrimaryButtonState mPrimaryButtonState;
-        final boolean mPatternEnabled;
+                @Override
+                public void onPatternCellAdded(List<Cell> pattern) {
+                }
 
-        /**
-         * @param message The message displayed as instruction.
-         * @param secondaryButtonState The state of the secondary button.
-         * @param primaryButtonState The state of the primary button.
-         * @param patternEnabled Whether the pattern widget is mEnabled.
-         */
-        Stage(int messageId,
-                SecondaryButtonState secondaryButtonState,
-                PrimaryButtonState primaryButtonState,
-                boolean patternEnabled) {
-            this.mMessageId = messageId;
-            this.mSecondaryButtonState = secondaryButtonState;
-            this.mPrimaryButtonState = primaryButtonState;
-            this.mPatternEnabled = patternEnabled;
-        }
-    }
+                private void handleConfirmPattern(List<LockPatternView.Cell> pattern) {
+                    if (mChosenPattern == null) {
+                        throw new IllegalStateException(
+                                "null chosen pattern in stage 'need to confirm");
+                    }
+                    if (mChosenPattern.equals(pattern)) {
+                        updateStage(Stage.ChoiceConfirmed);
+                    } else {
+                        updateStage(Stage.ConfirmWrong);
+                    }
+                }
 
-    /**
-     * The states of the primary footer button.
-     */
-    enum PrimaryButtonState {
-        Continue(R.string.continue_button_text, true),
-        ContinueDisabled(R.string.continue_button_text, false),
-        Confirm(R.string.lockpattern_confirm_button_text, true),
-        ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
-        Retry(R.string.lockscreen_retry_button_text, true),
-        Ok(R.string.okay, true);
-
-        /**
-         * @param text The displayed mText for this mode.
-         * @param enabled Whether the button should be mEnabled.
-         */
-        PrimaryButtonState(int text, boolean enabled) {
-            this.mText = text;
-            this.mEnabled = enabled;
-        }
-
-        final int mText;
-        final boolean mEnabled;
-    }
-
-    /**
-     * The states of the secondary footer button.
-     */
-    enum SecondaryButtonState {
-        Cancel(R.string.lockpattern_cancel_button_text, true),
-        CancelDisabled(R.string.lockpattern_cancel_button_text, false),
-        Retry(R.string.lockpattern_retry_button_text, true),
-        RetryDisabled(R.string.lockpattern_retry_button_text, false),
-        Gone(ID_EMPTY_MESSAGE, false);
-
-        /**
-         * @param text The displayed mText for this mode.
-         * @param enabled Whether the button should be mEnabled.
-         */
-        SecondaryButtonState(int textId, boolean enabled) {
-            this.mTextResId = textId;
-            this.mEnabled = enabled;
-        }
-
-        final int mTextResId;
-        final boolean mEnabled;
-    }
+                private void handlePatternEntered(List<LockPatternView.Cell> pattern) {
+                    if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+                        updateStage(Stage.ChoiceTooShort);
+                    } else {
+                        mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern);
+                        updateStage(Stage.FirstChoiceValid);
+                    }
+                }
+            };
 
     /**
      * Factory method for creating ChooseLockPatternFragment
      */
-    public static ChooseLockPatternFragment newInstance(boolean isInSetupWizard) {
+    public static ChooseLockPatternFragment newInstance() {
         ChooseLockPatternFragment patternFragment = new ChooseLockPatternFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.security_lock_pattern);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, isInSetupWizard
-                ? R.layout.suw_action_bar_with_button : R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.choose_lock_pattern);
-        patternFragment.setArguments(bundle);
         return patternFragment;
     }
 
     @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return R.layout.choose_lock_pattern;
+    }
+
+    @Override
+    @StringRes
+    protected int getTitleId() {
+        return R.string.security_lock_pattern;
+    }
+
+    @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mWrongPatternClearTimeOut = getResources().getInteger(R.integer.clear_content_timeout_ms);
@@ -223,8 +168,13 @@
 
         Bundle args = getArguments();
         if (args != null) {
-            mIsInSetupWizard = args.getBoolean(BaseFragment.EXTRA_RUNNING_IN_SETUP_WIZARD);
-            mCurrentPattern = args.getString(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK);
+            mCurrentPattern = args.getByteArray(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK);
+        }
+
+        if (savedInstanceState != null) {
+            mUiStage = Stage.values()[savedInstanceState.getInt(STATE_UI_STAGE)];
+            mChosenPattern = LockPatternUtils.byteArrayToPattern(
+                    savedInstanceState.getByteArray(STATE_CHOSEN_PATTERN));
         }
     }
 
@@ -242,14 +192,6 @@
         mLockPatternView.clearPattern();
         mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
 
-        if (mIsInSetupWizard) {
-            View screenLockOptions = view.findViewById(R.id.screen_lock_options);
-            screenLockOptions.setVisibility(View.VISIBLE);
-            screenLockOptions.setOnClickListener(v -> {
-                new LockTypeDialogFragment().show(getFragmentManager(), LOCK_OPTIONS_DIALOG_TAG);
-            });
-        }
-
         // Re-attach to the exiting worker if there is one.
         if (savedInstanceState != null) {
             mSavePatternWorker = (SavePatternWorker) getFragmentManager().findFragmentByTag(
@@ -260,15 +202,11 @@
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
+        mProgressBar = requireActivity().findViewById(R.id.progress_bar);
 
-        // Don't show toolbar title in Setup Wizard
-        if (mIsInSetupWizard) {
-            ((TextView) getActivity().findViewById(R.id.title)).setText("");
-        }
-
-        mPrimaryButton = getActivity().findViewById(R.id.action_button1);
+        mPrimaryButton = requireActivity().findViewById(R.id.action_button1);
         mPrimaryButton.setOnClickListener(view -> handlePrimaryButtonClick());
-        mSecondaryButton = getActivity().findViewById(R.id.action_button2);
+        mSecondaryButton = requireActivity().findViewById(R.id.action_button2);
         mSecondaryButton.setVisibility(View.VISIBLE);
         mSecondaryButton.setOnClickListener(view -> handleSecondaryButtonClick());
     }
@@ -285,17 +223,27 @@
     }
 
     @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(STATE_UI_STAGE, mUiStage.ordinal());
+        outState.putByteArray(STATE_CHOSEN_PATTERN,
+                LockPatternUtils.patternToByteArray(mChosenPattern));
+    }
+
+    @Override
     public void onStop() {
         super.onStop();
         if (mSavePatternWorker != null) {
             mSavePatternWorker.setListener(null);
         }
+        mProgressBar.setVisibility(View.GONE);
     }
 
     /**
      * Updates the messages and buttons appropriate to what stage the user
      * is at in choosing a pattern. This doesn't handle clearing out the pattern;
      * the pattern is expected to be in the right state.
+     *
      * @param stage The stage UI should be updated to match with.
      */
     protected void updateStage(Stage stage) {
@@ -309,13 +257,7 @@
             setSecondaryButtonVisible(false);
         } else {
             setSecondaryButtonVisible(true);
-            // In Setup Wizard, the Cancel button text is replaced with Skip
-            if (mIsInSetupWizard && stage.mSecondaryButtonState.mTextResId
-                    == R.string.lockpattern_cancel_button_text) {
-                setSecondaryButtonText(R.string.lockscreen_skip_button_text);
-            } else {
-                setSecondaryButtonText(stage.mSecondaryButtonState.mTextResId);
-            }
+            setSecondaryButtonText(stage.mSecondaryButtonState.mTextResId);
             setSecondaryButtonEnabled(stage.mSecondaryButtonState.mEnabled);
         }
 
@@ -360,63 +302,6 @@
         }
     }
 
-    // The pattern listener that responds according to a user choosing a new
-    // lock pattern.
-    private final LockPatternView.OnPatternListener mChooseNewLockPatternListener =
-            new LockPatternView.OnPatternListener() {
-                @Override
-                public void onPatternStart() {
-                    mLockPatternView.removeCallbacks(mClearPatternRunnable);
-                    updateUIWhenPatternInProgress();
-                }
-
-                @Override
-                public void onPatternCleared() {
-                    mLockPatternView.removeCallbacks(mClearPatternRunnable);
-                }
-
-                @Override
-                public void onPatternDetected(List<LockPatternView.Cell> pattern) {
-                    switch(mUiStage) {
-                        case Introduction:
-                        case ChoiceTooShort:
-                            handlePatternEntered(pattern);
-                            break;
-                        case ConfirmWrong:
-                        case NeedToConfirm:
-                            handleConfirmPattern(pattern);
-                            break;
-                        default:
-                            throw new IllegalStateException("Unexpected stage " + mUiStage
-                                    + " when entering the pattern.");
-                    }
-                }
-
-                @Override
-                public void onPatternCellAdded(List<Cell> pattern) {}
-
-                private void handleConfirmPattern(List<LockPatternView.Cell> pattern) {
-                    if (mChosenPattern == null) {
-                        throw new IllegalStateException(
-                                "null chosen pattern in stage 'need to confirm");
-                    }
-                    if (mChosenPattern.equals(pattern)) {
-                        updateStage(Stage.ChoiceConfirmed);
-                    } else {
-                        updateStage(Stage.ConfirmWrong);
-                    }
-                }
-
-                private void handlePatternEntered(List<LockPatternView.Cell> pattern) {
-                    if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
-                        updateStage(Stage.ChoiceTooShort);
-                    } else {
-                        mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern);
-                        updateStage(Stage.FirstChoiceValid);
-                    }
-                }
-            };
-
     private void updateUIWhenPatternInProgress() {
         mMessageText.setText(R.string.lockpattern_recording_inprogress);
         setPrimaryButtonEnabled(false);
@@ -450,23 +335,10 @@
         mSecondaryButton.setText(textId);
     }
 
-    /**
-     * The patten used during the help screen to show how to draw a pattern.
-     */
-    private final List<LockPatternView.Cell> mAnimatePattern =
-            Collections.unmodifiableList(Lists.newArrayList(
-                    LockPatternView.Cell.of(0, 0),
-                    LockPatternView.Cell.of(0, 1),
-                    LockPatternView.Cell.of(1, 1),
-                    LockPatternView.Cell.of(2, 1)
-            ));
-
-    private Runnable mClearPatternRunnable = () -> mLockPatternView.clearPattern();
-
     // Update display message and decide on next step according to the different mText
     // on the primary button
     private void handlePrimaryButtonClick() {
-        switch(mUiStage.mPrimaryButtonState) {
+        switch (mUiStage.mPrimaryButtonState) {
             case Continue:
                 if (mUiStage != Stage.FirstChoiceValid) {
                     throw new IllegalStateException("expected ui stage "
@@ -506,18 +378,14 @@
     // Update display message and proceed to next step according to the different mText on
     // the secondary button.
     private void handleSecondaryButtonClick() {
-        switch(mUiStage.mSecondaryButtonState) {
+        switch (mUiStage.mSecondaryButtonState) {
             case Retry:
                 mChosenPattern = null;
                 mLockPatternView.clearPattern();
                 updateStage(Stage.Introduction);
                 break;
             case Cancel:
-                if (mIsInSetupWizard) {
-                    ((SetupWizardScreenLockActivity) getActivity()).onCancel();
-                } else {
-                    getFragmentController().goBack();
-                }
+                getFragmentController().goBack();
                 break;
             default:
                 throw new IllegalStateException("secondary footer button pressed, but stage of "
@@ -525,8 +393,10 @@
         }
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting
     void onChosenLockSaveFinished(boolean isSaveSuccessful) {
+        mProgressBar.setVisibility(View.GONE);
+
         if (isSaveSuccessful) {
             onComplete();
         } else {
@@ -554,14 +424,162 @@
         }
 
         mSavePatternWorker.start(mUserId, mChosenPattern, mCurrentPattern);
+        mProgressBar.setVisibility(View.VISIBLE);
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting
     void onComplete() {
-        if (mIsInSetupWizard) {
-            ((SetupWizardScreenLockActivity) getActivity()).onComplete();
-        } else {
-            getActivity().finish();
+        if (mCurrentPattern != null) {
+            Arrays.fill(mCurrentPattern, (byte) 0);
+        }
+
+        getActivity().finish();
+    }
+
+    /**
+     * Keep track internally of where the user is in choosing a pattern.
+     */
+    enum Stage {
+        /**
+         * Initial stage when first launching choose a lock pattern.
+         * Pattern mEnabled, secondary button allow for Cancel, primary button disabled.
+         */
+        Introduction(
+                R.string.lockpattern_recording_intro_header,
+                SecondaryButtonState.Cancel,
+                PrimaryButtonState.ContinueDisabled,
+                /* patternEnabled= */ true),
+        /**
+         * Help screen to show how a valid pattern looks like.
+         * Pattern disabled, primary button shows Ok. No secondary button.
+         */
+        HelpScreen(
+                R.string.lockpattern_settings_help_how_to_record,
+                SecondaryButtonState.Gone,
+                PrimaryButtonState.Ok,
+                /* patternEnabled= */ false),
+        /**
+         * Invalid pattern is entered, hint message show required number of dots.
+         * Secondary button allows for Retry, primary button disabled.
+         */
+        ChoiceTooShort(
+                R.string.lockpattern_recording_incorrect_too_short,
+                SecondaryButtonState.Retry,
+                PrimaryButtonState.ContinueDisabled,
+                /* patternEnabled= */ true),
+        /**
+         * First drawing on the pattern is valid, primary button shows Continue,
+         * can proceed to next screen.
+         */
+        FirstChoiceValid(
+                R.string.lockpattern_recording_intro_header,
+                SecondaryButtonState.Retry,
+                PrimaryButtonState.Continue,
+                /* patternEnabled= */ false),
+        /**
+         * Need to draw pattern again to confirm.
+         * Secondary button allows for Cancel, primary button disabled.
+         */
+        NeedToConfirm(
+                R.string.lockpattern_need_to_confirm,
+                SecondaryButtonState.Cancel,
+                PrimaryButtonState.ConfirmDisabled,
+                /* patternEnabled= */ true),
+        /**
+         * Confirmation of previous drawn pattern failed, didn't enter the same pattern.
+         * Need to re-draw the pattern to match the fist pattern.
+         */
+        ConfirmWrong(
+                R.string.lockpattern_pattern_wrong,
+                SecondaryButtonState.Cancel,
+                PrimaryButtonState.ConfirmDisabled,
+                /* patternEnabled= */ true),
+        /**
+         * Pattern is confirmed after drawing the same pattern twice.
+         * Pattern disabled.
+         */
+        ChoiceConfirmed(
+                R.string.lockpattern_pattern_confirmed,
+                SecondaryButtonState.Cancel,
+                PrimaryButtonState.Confirm,
+                /* patternEnabled= */ false),
+
+        /**
+         * Error saving pattern.
+         * Pattern disabled, primary button shows Retry, secondary button allows for cancel
+         */
+        SaveFailure(
+                R.string.error_saving_lockpattern,
+                SecondaryButtonState.Cancel,
+                PrimaryButtonState.Retry,
+                /* patternEnabled= */ false);
+
+        final int mMessageId;
+        final SecondaryButtonState mSecondaryButtonState;
+        final PrimaryButtonState mPrimaryButtonState;
+        final boolean mPatternEnabled;
+
+        /**
+         * @param messageId            The message displayed as instruction.
+         * @param secondaryButtonState The state of the secondary button.
+         * @param primaryButtonState   The state of the primary button.
+         * @param patternEnabled       Whether the pattern widget is mEnabled.
+         */
+        Stage(@StringRes int messageId,
+                SecondaryButtonState secondaryButtonState,
+                PrimaryButtonState primaryButtonState,
+                boolean patternEnabled) {
+            this.mMessageId = messageId;
+            this.mSecondaryButtonState = secondaryButtonState;
+            this.mPrimaryButtonState = primaryButtonState;
+            this.mPatternEnabled = patternEnabled;
+        }
+    }
+
+    /**
+     * The states of the primary footer button.
+     */
+    enum PrimaryButtonState {
+        Continue(R.string.continue_button_text, true),
+        ContinueDisabled(R.string.continue_button_text, false),
+        Confirm(R.string.lockpattern_confirm_button_text, true),
+        ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
+        Retry(R.string.lockscreen_retry_button_text, true),
+        Ok(R.string.okay, true);
+
+        final int mText;
+        final boolean mEnabled;
+
+        /**
+         * @param text    The displayed mText for this mode.
+         * @param enabled Whether the button should be mEnabled.
+         */
+        PrimaryButtonState(@StringRes int text, boolean enabled) {
+            this.mText = text;
+            this.mEnabled = enabled;
+        }
+    }
+
+    /**
+     * The states of the secondary footer button.
+     */
+    enum SecondaryButtonState {
+        Cancel(R.string.lockpattern_cancel_button_text, true),
+        CancelDisabled(R.string.lockpattern_cancel_button_text, false),
+        Retry(R.string.lockpattern_retry_button_text, true),
+        RetryDisabled(R.string.lockpattern_retry_button_text, false),
+        Gone(ID_EMPTY_MESSAGE, false);
+
+        final int mTextResId;
+        final boolean mEnabled;
+
+        /**
+         * @param textId  The displayed mText for this mode.
+         * @param enabled Whether the button should be mEnabled.
+         */
+        SecondaryButtonState(@StringRes int textId, boolean enabled) {
+            this.mTextResId = textId;
+            this.mEnabled = enabled;
         }
     }
 }
diff --git a/src/com/android/car/settings/security/ChooseLockPinPasswordFragment.java b/src/com/android/car/settings/security/ChooseLockPinPasswordFragment.java
index 19ff5ad..793ea58 100644
--- a/src/com/android/car/settings/security/ChooseLockPinPasswordFragment.java
+++ b/src/com/android/car/settings/security/ChooseLockPinPasswordFragment.java
@@ -16,15 +16,12 @@
 
 package com.android.car.settings.security;
 
-import android.annotation.DrawableRes;
-import android.annotation.StringRes;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.UserHandle;
-import android.support.annotation.VisibleForTesting;
 import android.text.Editable;
 import android.text.Selection;
 import android.text.Spannable;
@@ -34,13 +31,22 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import androidx.annotation.DrawableRes;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
 import com.android.car.settings.common.Logger;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.TextViewInputDisabler;
 
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -50,6 +56,8 @@
 
     private static final String LOCK_OPTIONS_DIALOG_TAG = "lock_options_dialog_tag";
     private static final String FRAGMENT_TAG_SAVE_PASSWORD_WORKER = "save_password_worker";
+    private static final String STATE_UI_STAGE = "state_ui_stage";
+    private static final String STATE_FIRST_ENTRY = "state_first_entry";
     private static final Logger LOG = new Logger(ChooseLockPinPasswordFragment.class);
     private static final String EXTRA_IS_PIN = "extra_is_pin";
 
@@ -58,104 +66,34 @@
     private int mUserId;
     private int mErrorCode = PasswordHelper.NO_ERROR;
 
-    private boolean mIsInSetupWizard;
     private boolean mIsPin;
     private boolean mIsAlphaMode;
 
     // Password currently in the input field
-    private String mCurrentEntry;
+    private byte[] mCurrentEntry;
     // Existing password that user previously set
-    private String mExistingPassword;
+    private byte[] mExistingPassword;
     // Password must be entered twice.  This is what user entered the first time.
-    private String mFirstEntry;
+    private byte[] mFirstEntry;
 
     private PinPadView mPinPad;
     private TextView mHintMessage;
     private Button mSecondaryButton;
     private Button mPrimaryButton;
     private EditText mPasswordField;
+    private ProgressBar mProgressBar;
 
     private TextChangedHandler mTextChangedHandler = new TextChangedHandler();
     private TextViewInputDisabler mPasswordEntryInputDisabler;
     private SavePasswordWorker mSavePasswordWorker;
     private PasswordHelper mPasswordHelper;
 
-    // Keep track internally of where the user is in choosing a password.
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    enum Stage {
-        Introduction(
-                R.string.choose_lock_password_hints,
-                R.string.choose_lock_pin_hints,
-                R.string.continue_button_text,
-                R.string.lockpassword_cancel_label,
-                R.drawable.ic_arrow_forward_on_disc),
-
-        PasswordInvalid(
-                R.string.lockpassword_invalid_password,
-                R.string.lockpin_invalid_pin,
-                R.string.continue_button_text,
-                R.string.lockpassword_clear_label,
-                R.drawable.ic_arrow_forward_on_disc),
-
-        NeedToConfirm(
-                R.string.confirm_your_password_header,
-                R.string.confirm_your_pin_header,
-                R.string.lockpassword_confirm_label,
-                R.string.lockpassword_cancel_label,
-                R.drawable.ic_check_on_disc),
-
-        ConfirmWrong(
-                R.string.confirm_passwords_dont_match,
-                R.string.confirm_pins_dont_match,
-                R.string.continue_button_text,
-                R.string.lockpassword_cancel_label,
-                R.drawable.ic_check_on_disc),
-
-        SaveFailure(
-                R.string.error_saving_password,
-                R.string.error_saving_lockpin,
-                R.string.lockscreen_retry_button_text,
-                R.string.lockpassword_cancel_label,
-                R.drawable.ic_check_on_disc);
-
-        public final int alphaHint;
-        public final int numericHint;
-        public final int primaryButtonText;
-        public final int secondaryButtonText;
-        public final int enterKeyIcon;
-
-        Stage(@StringRes int hintInAlpha,
-                @StringRes int hintInNumeric,
-                @StringRes int primaryButtonText,
-                @StringRes int secondaryButtonText,
-                @DrawableRes int enterKeyIcon) {
-            this.alphaHint = hintInAlpha;
-            this.numericHint = hintInNumeric;
-            this.primaryButtonText = primaryButtonText;
-            this.secondaryButtonText = secondaryButtonText;
-            this.enterKeyIcon = enterKeyIcon;
-        }
-
-        @StringRes
-        public int getHint(boolean isAlpha) {
-            if (isAlpha) {
-                return alphaHint;
-            } else {
-                return numericHint;
-            }
-        }
-    }
-
     /**
      * Factory method for creating fragment in password mode
      */
-    public static ChooseLockPinPasswordFragment newPasswordInstance(boolean isInSetupWizard) {
+    public static ChooseLockPinPasswordFragment newPasswordInstance() {
         ChooseLockPinPasswordFragment passwordFragment = new ChooseLockPinPasswordFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.security_lock_password);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, isInSetupWizard
-                ? R.layout.suw_action_bar_with_button : R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.choose_lock_password);
+        Bundle bundle = new Bundle();
         bundle.putBoolean(EXTRA_IS_PIN, false);
         passwordFragment.setArguments(bundle);
         return passwordFragment;
@@ -164,28 +102,41 @@
     /**
      * Factory method for creating fragment in Pin mode
      */
-    public static ChooseLockPinPasswordFragment newPinInstance(boolean isInSetupWizard) {
+    public static ChooseLockPinPasswordFragment newPinInstance() {
         ChooseLockPinPasswordFragment passwordFragment = new ChooseLockPinPasswordFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.security_lock_pin);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, isInSetupWizard
-                ? R.layout.suw_action_bar_with_button : R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.choose_lock_pin);
+        Bundle bundle = new Bundle();
         bundle.putBoolean(EXTRA_IS_PIN, true);
         passwordFragment.setArguments(bundle);
         return passwordFragment;
     }
 
     @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return mIsPin ? R.layout.choose_lock_pin : R.layout.choose_lock_password;
+    }
+
+    @Override
+    @StringRes
+    protected int getTitleId() {
+        return mIsPin ? R.string.security_lock_pin : R.string.security_lock_password;
+    }
+
+    @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mUserId = UserHandle.myUserId();
 
         Bundle args = getArguments();
         if (args != null) {
-            mIsInSetupWizard = args.getBoolean(BaseFragment.EXTRA_RUNNING_IN_SETUP_WIZARD);
             mIsPin = args.getBoolean(EXTRA_IS_PIN);
-            mExistingPassword = args.getString(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK);
+            mExistingPassword = args.getByteArray(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK);
         }
 
         mPasswordHelper = new PasswordHelper(mIsPin);
@@ -194,6 +145,11 @@
         mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == passwordQuality
                 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == passwordQuality
                 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == passwordQuality;
+
+        if (savedInstanceState != null) {
+            mUiStage = Stage.values()[savedInstanceState.getInt(STATE_UI_STAGE)];
+            mFirstEntry = savedInstanceState.getByteArray(STATE_FIRST_ENTRY);
+        }
     }
 
     @Override
@@ -239,14 +195,6 @@
 
         mHintMessage = view.findViewById(R.id.hint_text);
 
-        if (mIsInSetupWizard) {
-            View screenLockOptions = view.findViewById(R.id.screen_lock_options);
-            screenLockOptions.setVisibility(View.VISIBLE);
-            screenLockOptions.setOnClickListener(v -> {
-                new LockTypeDialogFragment().show(getFragmentManager(), LOCK_OPTIONS_DIALOG_TAG);
-            });
-        }
-
         if (mIsPin) {
             initPinView(view);
         } else {
@@ -268,13 +216,9 @@
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
+        mProgressBar = requireActivity().findViewById(R.id.progress_bar);
 
-        // Don't show toolbar title in Setup Wizard
-        if (mIsInSetupWizard) {
-            ((TextView) getActivity().findViewById(R.id.title)).setText("");
-        }
-
-        mPrimaryButton = getActivity().findViewById(R.id.action_button1);
+        mPrimaryButton = requireActivity().findViewById(R.id.action_button1);
         mPrimaryButton.setOnClickListener(view -> handlePrimaryButtonClick());
         mSecondaryButton = getActivity().findViewById(R.id.action_button2);
         mSecondaryButton.setVisibility(View.VISIBLE);
@@ -292,11 +236,19 @@
     }
 
     @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(STATE_UI_STAGE, mUiStage.ordinal());
+        outState.putByteArray(STATE_FIRST_ENTRY, mFirstEntry);
+    }
+
+    @Override
     public void onStop() {
         super.onStop();
         if (mSavePasswordWorker != null) {
             mSavePasswordWorker.setListener(null);
         }
+        mProgressBar.setVisibility(View.GONE);
     }
 
     /**
@@ -307,23 +259,15 @@
     }
 
     /**
-     * Populate the password entry field with the argument
-     */
-    private void setPasswordField(String text) {
-        mPasswordField.setText(text);
-    }
-
-    /**
      * Returns the string in the password entry field
      */
-    private String getEnteredPassword() {
-        return mPasswordField.getText().toString();
+    @Nullable
+    private byte[] getEnteredPassword() {
+        return LockPatternUtils.charSequenceToByteArray(mPasswordField.getText());
     }
 
     private void initPinView(View view) {
-        mPinPad = (PinPadView) view.findViewById(R.id.pin_pad);
-        // ChooseLockPin fragment sets the icon dynamically and doesn't use the default tint.
-        mPinPad.setEnterKeyImageTint(null);
+        mPinPad = view.findViewById(R.id.pin_pad);
 
         PinPadView.PinPadClickListener pinPadClickListener = new PinPadView.PinPadClickListener() {
             @Override
@@ -333,9 +277,13 @@
 
             @Override
             public void onBackspaceClick() {
-                String pin = getEnteredPassword();
-                if (pin.length() > 0) {
-                    setPasswordField(pin.substring(0, pin.length() - 1));
+                byte[] pin = getEnteredPassword();
+                if (pin != null && pin.length > 0) {
+                    mPasswordField.getText().delete(mPasswordField.getSelectionEnd() - 1,
+                            mPasswordField.getSelectionEnd());
+                }
+                if (pin != null) {
+                    Arrays.fill(pin, (byte) 0);
                 }
             }
 
@@ -349,7 +297,8 @@
     }
 
     private boolean shouldEnableSubmit() {
-        return getEnteredPassword().length() >= PasswordHelper.MIN_LENGTH
+        return getEnteredPassword() != null
+                && getEnteredPassword().length >= PasswordHelper.MIN_LENGTH
                 && (mSavePasswordWorker == null || mSavePasswordWorker.isFinished());
     }
 
@@ -384,7 +333,7 @@
 
         mCurrentEntry = getEnteredPassword();
 
-        switch(mUiStage) {
+        switch (mUiStage) {
             case Introduction:
                 mErrorCode = mPasswordHelper.validate(mCurrentEntry);
                 if (mErrorCode == PasswordHelper.NO_ERROR) {
@@ -393,13 +342,14 @@
                     updateStage(Stage.NeedToConfirm);
                 } else {
                     updateStage(Stage.PasswordInvalid);
+                    Arrays.fill(mCurrentEntry, (byte) 0);
                 }
                 break;
             case NeedToConfirm:
             case SaveFailure:
                 // Password must be entered twice. mFirstEntry is the one the user entered
                 // the first time.  mCurrentEntry is what's currently in the input field
-                if (mFirstEntry.equals(mCurrentEntry)) {
+                if (Arrays.equals(mFirstEntry, mCurrentEntry)) {
                     startSaveAndFinish();
                 } else {
                     CharSequence tmp = mPasswordField.getText();
@@ -407,6 +357,7 @@
                         Selection.setSelection((Spannable) tmp, 0, tmp.length());
                     }
                     updateStage(Stage.ConfirmWrong);
+                    Arrays.fill(mCurrentEntry, (byte) 0);
                 }
                 break;
             default:
@@ -424,23 +375,15 @@
         if (mUiStage.secondaryButtonText == R.string.lockpassword_clear_label) {
             mPasswordField.setText("");
             mUiStage = Stage.Introduction;
-            if (mIsInSetupWizard && mUiStage.secondaryButtonText
-                    == R.string.lockpassword_cancel_label) {
-                setSecondaryButtonText(R.string.lockscreen_skip_button_text);
-            } else {
-                setSecondaryButtonText(mUiStage.secondaryButtonText);
-            }
+            setSecondaryButtonText(mUiStage.secondaryButtonText);
         } else {
-            if (mIsInSetupWizard) {
-                ((SetupWizardScreenLockActivity) getActivity()).onCancel();
-            } else {
-                getFragmentController().goBack();
-            }
+            getFragmentController().goBack();
         }
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting
     void onChosenLockSaveFinished(boolean isSaveSuccessful) {
+        mProgressBar.setVisibility(View.GONE);
         if (isSaveSuccessful) {
             onComplete();
         } else {
@@ -470,6 +413,7 @@
         mSavePasswordWorker.start(mUserId, mCurrentEntry, mExistingPassword,
                 mPasswordHelper.getPasswordQuality());
 
+        mProgressBar.setVisibility(View.VISIBLE);
         updateSubmitButtonsState();
     }
 
@@ -487,7 +431,7 @@
             mPinPad.setEnterKeyIcon(mUiStage.enterKeyIcon);
         }
 
-        switch(mUiStage) {
+        switch (mUiStage) {
             case Introduction:
             case NeedToConfirm:
                 mPasswordField.setError(null);
@@ -507,12 +451,7 @@
         }
 
         setPrimaryButtonText(mUiStage.primaryButtonText);
-        if (mIsInSetupWizard && mUiStage.secondaryButtonText
-                == R.string.lockpassword_cancel_label) {
-            setSecondaryButtonText(R.string.lockscreen_skip_button_text);
-        } else {
-            setSecondaryButtonText(mUiStage.secondaryButtonText);
-        }
+        setSecondaryButtonText(mUiStage.secondaryButtonText);
         mPasswordEntryInputDisabler.setInputEnabled(inputAllowed);
     }
 
@@ -525,18 +464,94 @@
         mHintMessage.setText(message);
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting
     void updateStage(Stage stage) {
         mUiStage = stage;
         updateUi();
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting
     void onComplete() {
-        if (mIsInSetupWizard) {
-            ((SetupWizardScreenLockActivity) getActivity()).onComplete();
-        } else {
-            getActivity().finish();
+        if (mCurrentEntry != null) {
+            Arrays.fill(mCurrentEntry, (byte) 0);
+        }
+
+        if (mExistingPassword != null) {
+            Arrays.fill(mExistingPassword, (byte) 0);
+        }
+
+        if (mFirstEntry != null) {
+            Arrays.fill(mFirstEntry, (byte) 0);
+        }
+
+        mPasswordField.setText("");
+
+        getActivity().finish();
+    }
+
+    // Keep track internally of where the user is in choosing a password.
+    @VisibleForTesting
+    enum Stage {
+        Introduction(
+                R.string.choose_lock_password_hints,
+                R.string.choose_lock_pin_hints,
+                R.string.continue_button_text,
+                R.string.lockpassword_cancel_label,
+                R.drawable.ic_arrow_forward),
+
+        PasswordInvalid(
+                R.string.lockpassword_invalid_password,
+                R.string.lockpin_invalid_pin,
+                R.string.continue_button_text,
+                R.string.lockpassword_clear_label,
+                R.drawable.ic_arrow_forward),
+
+        NeedToConfirm(
+                R.string.confirm_your_password_header,
+                R.string.confirm_your_pin_header,
+                R.string.lockpassword_confirm_label,
+                R.string.lockpassword_cancel_label,
+                R.drawable.ic_check),
+
+        ConfirmWrong(
+                R.string.confirm_passwords_dont_match,
+                R.string.confirm_pins_dont_match,
+                R.string.continue_button_text,
+                R.string.lockpassword_cancel_label,
+                R.drawable.ic_check),
+
+        SaveFailure(
+                R.string.error_saving_password,
+                R.string.error_saving_lockpin,
+                R.string.lockscreen_retry_button_text,
+                R.string.lockpassword_cancel_label,
+                R.drawable.ic_check);
+
+        public final int alphaHint;
+        public final int numericHint;
+        public final int primaryButtonText;
+        public final int secondaryButtonText;
+        public final int enterKeyIcon;
+
+        Stage(@StringRes int hintInAlpha,
+                @StringRes int hintInNumeric,
+                @StringRes int primaryButtonText,
+                @StringRes int secondaryButtonText,
+                @DrawableRes int enterKeyIcon) {
+            this.alphaHint = hintInAlpha;
+            this.numericHint = hintInNumeric;
+            this.primaryButtonText = primaryButtonText;
+            this.secondaryButtonText = secondaryButtonText;
+            this.enterKeyIcon = enterKeyIcon;
+        }
+
+        @StringRes
+        public int getHint(boolean isAlpha) {
+            if (isAlpha) {
+                return alphaHint;
+            } else {
+                return numericHint;
+            }
         }
     }
 
diff --git a/src/com/android/car/settings/security/ChooseLockTypeFragment.java b/src/com/android/car/settings/security/ChooseLockTypeFragment.java
index 2221060..b9959a1 100644
--- a/src/com/android/car/settings/security/ChooseLockTypeFragment.java
+++ b/src/com/android/car/settings/security/ChooseLockTypeFragment.java
@@ -16,19 +16,13 @@
 
 package com.android.car.settings.security;
 
-import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.os.Bundle;
-import android.os.UserHandle;
-import android.text.TextUtils;
 
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.car.settings.common.SettingsFragment;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -36,127 +30,37 @@
 /**
  * Give user choices of lock screen type: Pin/Pattern/Password or None.
  */
-public class ChooseLockTypeFragment extends ListItemSettingsFragment {
+public class ChooseLockTypeFragment extends SettingsFragment {
 
     public static final String EXTRA_CURRENT_PASSWORD_QUALITY = "extra_current_password_quality";
-    private static final String DIALOG_TAG = "ConfirmRemoveScreenLockDialog";
 
-    private ListItemProvider mItemProvider;
-    private String mCurrPassword;
+    private byte[] mCurrPassword;
     private int mPasswordQuality;
 
-    private final ConfirmRemoveScreenLockDialog.ConfirmRemoveLockListener mConfirmListener = () -> {
-        int userId = UserHandle.myUserId();
-        byte[] passwordBytes = mCurrPassword != null ? mCurrPassword.getBytes() : null;
-        new LockPatternUtils(getContext()).clearLock(passwordBytes, userId);
-        getFragmentController().goBack();
-    };
-
-    public static ChooseLockTypeFragment newInstance() {
-        ChooseLockTypeFragment chooseLockTypeFragment = new ChooseLockTypeFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.lock_settings_picker_title);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        chooseLockTypeFragment.setArguments(bundle);
-        return chooseLockTypeFragment;
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.choose_lock_type_fragment;
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public void onAttach(Context context) {
+        super.onAttach(context);
         Bundle args = getArguments();
         if (args != null) {
-            mCurrPassword = args.getString(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK);
+            mCurrPassword = args.getByteArray(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK);
             mPasswordQuality = args.getInt(EXTRA_CURRENT_PASSWORD_QUALITY);
         }
 
-        if (savedInstanceState != null) {
-            ConfirmRemoveScreenLockDialog dialog = (ConfirmRemoveScreenLockDialog)
-                    getFragmentManager().findFragmentByTag(DIALOG_TAG);
-            if (dialog != null) {
-                dialog.setConfirmRemoveLockListener(mConfirmListener);
-            }
+        List<LockTypeBasePreferenceController> controllers = new ArrayList<>();
+        controllers.add(use(NoLockPreferenceController.class, R.string.pk_no_lock));
+        controllers.add(use(PatternLockPreferenceController.class, R.string.pk_pattern_lock));
+        controllers.add(use(PasswordLockPreferenceController.class, R.string.pk_password_lock));
+        controllers.add(use(PinLockPreferenceController.class, R.string.pk_pin_lock));
+
+        for (LockTypeBasePreferenceController controller : controllers) {
+            controller.setCurrentPassword(mCurrPassword);
+            controller.setCurrentPasswordQuality(mPasswordQuality);
         }
     }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        if (mItemProvider == null) {
-            mItemProvider = new ListItemProvider.ListProvider(getListItems());
-        }
-        return mItemProvider;
-    }
-
-    private List<ListItem> getListItems() {
-        List<ListItem> items = new ArrayList<>();
-        items.add(createNoneLineItem());
-        items.add(createLockPatternLineItem());
-        items.add(createLockPasswordLineItem());
-        items.add(createLockPinLineItem());
-        return items;
-    }
-
-    private ListItem createNoneLineItem() {
-        TextListItem item = new TextListItem(getContext());
-        item.setTitle(getString(R.string.security_lock_none));
-        if (mPasswordQuality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-            item.setBody(getString(R.string.current_screen_lock));
-            // TODO set item disabled after b/78784323 is fixed
-        } else {
-            item.setOnClickListener(view -> {
-                ConfirmRemoveScreenLockDialog dialog = new ConfirmRemoveScreenLockDialog();
-                dialog.setConfirmRemoveLockListener(mConfirmListener);
-                dialog.show(getFragmentManager(), DIALOG_TAG);
-            });
-        }
-        return item;
-    }
-
-    private ListItem createLockPatternLineItem() {
-        TextListItem item = new TextListItem(getContext());
-        item.setTitle(getString(R.string.security_lock_pattern));
-        if (mPasswordQuality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
-            item.setBody(getString(R.string.current_screen_lock));
-        }
-        item.setOnClickListener(view -> launchFragment(
-                ChooseLockPatternFragment.newInstance(/* isInSetupWizard= */ false)));
-        return item;
-    }
-
-    private ListItem createLockPasswordLineItem() {
-        TextListItem item = new TextListItem(getContext());
-        item.setTitle(getString(R.string.security_lock_password));
-        if (mPasswordQuality == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
-                || mPasswordQuality == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
-            item.setBody(getString(R.string.current_screen_lock));
-        }
-        item.setOnClickListener(view -> launchFragment(
-                ChooseLockPinPasswordFragment.newPasswordInstance(/* isInSetupWizard= */ false)));
-        return item;
-    }
-
-    private ListItem createLockPinLineItem() {
-        TextListItem item = new TextListItem(getContext());
-        item.setTitle(getString(R.string.security_lock_pin));
-        if (mPasswordQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-                || mPasswordQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX) {
-            item.setBody(getString(R.string.current_screen_lock));
-        }
-        item.setOnClickListener(view -> launchFragment(
-                ChooseLockPinPasswordFragment.newPinInstance(/* isInSetupWizard= */ false)));
-        return item;
-    }
-
-    private void launchFragment(BaseFragment fragment) {
-        if (!TextUtils.isEmpty(mCurrPassword)) {
-            Bundle args = fragment.getArguments();
-            if (args == null) {
-                args = new Bundle();
-            }
-            args.putString(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK, mCurrPassword);
-            fragment.setArguments(args);
-        }
-
-        getFragmentController().launchFragment(fragment);
-    }
 }
diff --git a/src/com/android/car/settings/security/ConfirmLockPatternFragment.java b/src/com/android/car/settings/security/ConfirmLockPatternFragment.java
index 58d5e4f..afe3d97 100644
--- a/src/com/android/car/settings/security/ConfirmLockPatternFragment.java
+++ b/src/com/android/car/settings/security/ConfirmLockPatternFragment.java
@@ -20,9 +20,11 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.view.View;
-import android.widget.Button;
 import android.widget.TextView;
 
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
+
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
 import com.android.internal.widget.LockPatternUtils;
@@ -47,21 +49,24 @@
     private CheckLockListener mCheckLockListener;
 
     private int mUserId;
-    private boolean mIsInSetupWizard;
     private List<LockPatternView.Cell> mPattern;
 
-    /**
-     * Factory method for creating ConfirmLockPatternFragment
-     */
-    public static ConfirmLockPatternFragment newInstance(boolean isInSetupWizard) {
-        ConfirmLockPatternFragment patternFragment = new ConfirmLockPatternFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.security_settings_title);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, isInSetupWizard
-                ? R.layout.suw_action_bar_with_button : R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.confirm_lock_pattern_fragment);
-        patternFragment.setArguments(bundle);
-        return patternFragment;
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return R.layout.confirm_lock_pattern;
+    }
+
+    @Override
+    @StringRes
+    protected int getTitleId() {
+        return R.string.security_settings_title;
     }
 
     @Override
@@ -79,11 +84,6 @@
         super.onCreate(savedInstanceState);
         mLockPatternUtils = new LockPatternUtils(getContext());
         mUserId = UserHandle.myUserId();
-
-        Bundle args = getArguments();
-        if (args != null) {
-            mIsInSetupWizard = args.getBoolean(BaseFragment.EXTRA_RUNNING_IN_SETUP_WIZARD);
-        }
     }
 
     @Override
@@ -103,25 +103,6 @@
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        if (!mIsInSetupWizard) {
-            return;
-        }
-
-        // Don't show toolbar title in Setup Wizard.
-        ((TextView) getActivity().findViewById(R.id.title)).setText("");
-
-        Button mPrimaryButton = (Button) getActivity().findViewById(R.id.action_button1);
-        mPrimaryButton.setText(R.string.lockscreen_skip_button_text);
-        mPrimaryButton.setOnClickListener(v -> {
-            SetupWizardScreenLockActivity activity = (SetupWizardScreenLockActivity) getActivity();
-            activity.onCancel();
-        });
-    }
-
-    @Override
     public void onStart() {
         super.onStart();
         if (mCheckLockWorker != null) {
@@ -137,7 +118,7 @@
         }
     }
 
-    private Runnable mClearErrorRunnable = () ->  {
+    private Runnable mClearErrorRunnable = () -> {
         mLockPatternView.clearPattern();
         mMsgView.setText("");
     };
@@ -145,38 +126,40 @@
     private LockPatternView.OnPatternListener mLockPatternListener =
             new LockPatternView.OnPatternListener() {
 
-        public void onPatternStart() {
-            mLockPatternView.removeCallbacks(mClearErrorRunnable);
-            mMsgView.setText("");
-        }
+                public void onPatternStart() {
+                    mLockPatternView.removeCallbacks(mClearErrorRunnable);
+                    mMsgView.setText("");
+                }
 
-        public void onPatternCleared() {
-            mLockPatternView.removeCallbacks(mClearErrorRunnable);
-        }
+                public void onPatternCleared() {
+                    mLockPatternView.removeCallbacks(mClearErrorRunnable);
+                }
 
-        public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {}
+                public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
+                }
 
-        public void onPatternDetected(List<LockPatternView.Cell> pattern) {
-            mLockPatternView.setEnabled(false);
+                public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+                    mLockPatternView.setEnabled(false);
 
-            if (mCheckLockWorker == null) {
-                mCheckLockWorker = new CheckLockWorker();
-                mCheckLockWorker.setListener(ConfirmLockPatternFragment.this::onCheckCompleted);
+                    if (mCheckLockWorker == null) {
+                        mCheckLockWorker = new CheckLockWorker();
+                        mCheckLockWorker.setListener(
+                                ConfirmLockPatternFragment.this::onCheckCompleted);
 
-                getFragmentManager()
-                        .beginTransaction()
-                        .add(mCheckLockWorker, FRAGMENT_TAG_CHECK_LOCK_WORKER)
-                        .commitNow();
-            }
+                        getFragmentManager()
+                                .beginTransaction()
+                                .add(mCheckLockWorker, FRAGMENT_TAG_CHECK_LOCK_WORKER)
+                                .commitNow();
+                    }
 
-            mPattern = pattern;
-            mCheckLockWorker.checkPattern(mUserId, pattern);
-        }
-    };
+                    mPattern = pattern;
+                    mCheckLockWorker.checkPattern(mUserId, pattern);
+                }
+            };
 
     private void onCheckCompleted(boolean lockMatched) {
         if (lockMatched) {
-            mCheckLockListener.onLockVerified(LockPatternUtils.patternToString(mPattern));
+            mCheckLockListener.onLockVerified(LockPatternUtils.patternToByteArray(mPattern));
         } else {
             mLockPatternView.setEnabled(true);
             mMsgView.setText(R.string.lockpattern_pattern_wrong);
diff --git a/src/com/android/car/settings/security/ConfirmLockPinPasswordFragment.java b/src/com/android/car/settings/security/ConfirmLockPinPasswordFragment.java
index d1a7f3c..4b35912 100644
--- a/src/com/android/car/settings/security/ConfirmLockPinPasswordFragment.java
+++ b/src/com/android/car/settings/security/ConfirmLockPinPasswordFragment.java
@@ -19,19 +19,24 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.support.annotation.VisibleForTesting;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
 
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.Arrays;
 
 /**
  * Fragment for confirming existing lock PIN or password.  The containing activity must implement
@@ -51,19 +56,14 @@
 
     private int mUserId;
     private boolean mIsPin;
-    private boolean mIsInSetupWizard;
-    private String mEnteredPassword;
+    private byte[] mEnteredPassword;
 
     /**
      * Factory method for creating fragment in PIN mode.
      */
-    public static ConfirmLockPinPasswordFragment newPinInstance(boolean isInSetupWizard) {
+    public static ConfirmLockPinPasswordFragment newPinInstance() {
         ConfirmLockPinPasswordFragment patternFragment = new ConfirmLockPinPasswordFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.security_settings_title);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, isInSetupWizard
-                ? R.layout.suw_action_bar_with_button : R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.confirm_lock_pin_fragment);
+        Bundle bundle = new Bundle();
         bundle.putBoolean(EXTRA_IS_PIN, true);
         patternFragment.setArguments(bundle);
         return patternFragment;
@@ -72,19 +72,33 @@
     /**
      * Factory method for creating fragment in password mode.
      */
-    public static ConfirmLockPinPasswordFragment newPasswordInstance(boolean isInSetupWizard) {
+    public static ConfirmLockPinPasswordFragment newPasswordInstance() {
         ConfirmLockPinPasswordFragment patternFragment = new ConfirmLockPinPasswordFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.security_settings_title);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, isInSetupWizard
-                ? R.layout.suw_action_bar_with_button : R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.confirm_lock_password_fragment);
+        Bundle bundle = new Bundle();
         bundle.putBoolean(EXTRA_IS_PIN, false);
         patternFragment.setArguments(bundle);
         return patternFragment;
     }
 
     @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return mIsPin ? R.layout.confirm_lock_pin : R.layout.confirm_lock_password;
+    }
+
+    @Override
+    @StringRes
+    protected int getTitleId() {
+        return R.string.security_settings_title;
+    }
+
+    @Override
     public void onAttach(Context context) {
         super.onAttach(context);
         if ((getActivity() instanceof CheckLockListener)) {
@@ -100,7 +114,6 @@
         mUserId = UserHandle.myUserId();
         Bundle args = getArguments();
         if (args != null) {
-            mIsInSetupWizard = args.getBoolean(BaseFragment.EXTRA_RUNNING_IN_SETUP_WIZARD);
             mIsPin = args.getBoolean(EXTRA_IS_PIN);
         }
     }
@@ -109,8 +122,8 @@
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
 
-        mPasswordField = (EditText) view.findViewById(R.id.password_entry);
-        mMsgView = (TextView) view.findViewById(R.id.message);
+        mPasswordField = view.findViewById(R.id.password_entry);
+        mMsgView = view.findViewById(R.id.message);
 
         if (mIsPin) {
             initPinView(view);
@@ -125,25 +138,6 @@
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        if (!mIsInSetupWizard) {
-            return;
-        }
-
-        // Don't show toolbar title in Setup Wizard.
-        ((TextView) getActivity().findViewById(R.id.title)).setText("");
-
-        Button mPrimaryButton = (Button) getActivity().findViewById(R.id.action_button1);
-        mPrimaryButton.setText(R.string.lockscreen_skip_button_text);
-        mPrimaryButton.setOnClickListener(v -> {
-            SetupWizardScreenLockActivity activity = (SetupWizardScreenLockActivity) getActivity();
-            activity.onCancel();
-        });
-    }
-
-    @Override
     public void onStart() {
         super.onStart();
         if (mCheckLockWorker != null) {
@@ -172,7 +166,7 @@
     }
 
     private void initPinView(View view) {
-        mPinPad = (PinPadView) view.findViewById(R.id.pin_pad);
+        mPinPad = view.findViewById(R.id.pin_pad);
 
         PinPadView.PinPadClickListener pinPadClickListener = new PinPadView.PinPadClickListener() {
             @Override
@@ -184,16 +178,21 @@
             @Override
             public void onBackspaceClick() {
                 clearError();
-                String pin = mPasswordField.getText().toString();
-                if (pin.length() > 0) {
-                    mPasswordField.setText(pin.substring(0, pin.length() - 1));
+                byte[] pin = LockPatternUtils.charSequenceToByteArray(mPasswordField.getText());
+                if (pin != null && pin.length > 0) {
+                    mPasswordField.getText().delete(mPasswordField.getSelectionEnd() - 1,
+                            mPasswordField.getSelectionEnd());
+                }
+                if (pin != null) {
+                    Arrays.fill(pin, (byte) 0);
                 }
             }
 
             @Override
             public void onEnterKeyClick() {
-                mEnteredPassword = mPasswordField.getText().toString();
-                if (!TextUtils.isEmpty(mEnteredPassword)) {
+                mEnteredPassword = LockPatternUtils.charSequenceToByteArray(
+                        mPasswordField.getText());
+                if (mEnteredPassword != null) {
                     initCheckLockWorker();
                     mPinPad.setEnabled(false);
                     mCheckLockWorker.checkPinPassword(mUserId, mEnteredPassword);
@@ -213,7 +212,8 @@
 
                 initCheckLockWorker();
                 if (!mCheckLockWorker.isCheckInProgress()) {
-                    mEnteredPassword = mPasswordField.getText().toString();
+                    mEnteredPassword = LockPatternUtils.charSequenceToByteArray(
+                            mPasswordField.getText());
                     mCheckLockWorker.checkPinPassword(mUserId, mEnteredPassword);
                 }
                 return true;
@@ -223,10 +223,12 @@
 
         mPasswordField.addTextChangedListener(new TextWatcher() {
             @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
 
             @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {}
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+            }
 
             @Override
             public void afterTextChanged(Editable s) {
@@ -256,7 +258,7 @@
         }
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting
     void onCheckCompleted(boolean lockMatched) {
         if (lockMatched) {
             mCheckLockListener.onLockVerified(mEnteredPassword);
diff --git a/src/com/android/car/settings/security/ConfirmRemoveScreenLockDialog.java b/src/com/android/car/settings/security/ConfirmRemoveScreenLockDialog.java
index 2a0a97d..cd64872 100644
--- a/src/com/android/car/settings/security/ConfirmRemoveScreenLockDialog.java
+++ b/src/com/android/car/settings/security/ConfirmRemoveScreenLockDialog.java
@@ -16,57 +16,46 @@
 
 package com.android.car.settings.security;
 
+import android.app.AlertDialog;
 import android.app.Dialog;
-import android.content.DialogInterface;
 import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
 
-import androidx.car.app.CarAlertDialog;
+import androidx.fragment.app.DialogFragment;
 
 import com.android.car.settings.R;
 
 /**
  * Dialog to confirm screen lock removal.
  */
-public class ConfirmRemoveScreenLockDialog extends DialogFragment implements
-        DialogInterface.OnClickListener {
+public class ConfirmRemoveScreenLockDialog extends DialogFragment {
 
-    private ConfirmRemoveLockListener mListener;
+    /** Identifier for the dialog which confirms the removal of a screen lock. */
+    public static final String TAG = "confirm_remove_lock_dialog";
+    private ConfirmRemoveScreenLockListener mConfirmRemoveScreenLockListener;
 
-    /**
-     * Sets a listener for OnRemoveLockConfirmed that will get called if user confirms the dialog.
-     *
-     * @param listener Instance of {@link ConfirmRemoveLockListener} to call when confirmed.
-     */
-    public void setConfirmRemoveLockListener(ConfirmRemoveLockListener listener) {
-        mListener = listener;
+    /** Sets a listener to act when a user confirms delete. */
+    public void setConfirmRemoveScreenLockListener(ConfirmRemoveScreenLockListener listener) {
+        mConfirmRemoveScreenLockListener = listener;
     }
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        return new CarAlertDialog.Builder(getContext())
+        return new AlertDialog.Builder(getContext())
                 .setTitle(R.string.remove_screen_lock_title)
-                .setBody(R.string.remove_screen_lock_message)
-                .setPositiveButton(R.string.remove_button, this)
+                .setMessage(R.string.remove_screen_lock_message)
+                .setPositiveButton(R.string.remove_button, (dialog, which) -> {
+                    if (mConfirmRemoveScreenLockListener != null) {
+                        mConfirmRemoveScreenLockListener.onConfirmRemoveScreenLock();
+                    }
+                    dialog.dismiss();
+                })
                 .setNegativeButton(android.R.string.cancel, null)
                 .create();
     }
 
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        if (mListener != null) {
-            mListener.onRemoveLockConfirmed();
-        }
-    }
-
-    /**
-     * Interface for listeners that want to receive a callback when user confirms lock removal in
-     * the dialog.
-     */
-    public interface ConfirmRemoveLockListener {
-        /**
-         * Callback when user confirms locks removal.
-         */
-        void onRemoveLockConfirmed();
+    /** A listener for when user confirms lock removal for the current user. */
+    public interface ConfirmRemoveScreenLockListener {
+        /** Defines the actions to take when a user confirms the removal of a lock. */
+        void onConfirmRemoveScreenLock();
     }
 }
diff --git a/src/com/android/car/settings/security/LockTypeBasePreferenceController.java b/src/com/android/car/settings/security/LockTypeBasePreferenceController.java
new file mode 100644
index 0000000..d4be237
--- /dev/null
+++ b/src/com/android/car/settings/security/LockTypeBasePreferenceController.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Business Logic for security lock preferences. It can be extended to change which fragment should
+ * be opened when clicked.
+ */
+public abstract class LockTypeBasePreferenceController extends PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private byte[] mCurrentPassword;
+    private int mCurrentPasswordQuality;
+
+    public LockTypeBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /**
+     * Fragment specified here will be opened when the Preference is clicked. Return null to prevent
+     * navigation.
+     */
+    protected abstract Fragment fragmentToOpen();
+
+    /**
+     * If the current password quality is one of the values returned by this function, the
+     * controller will identify as having the current lock.
+     */
+    protected abstract int[] allowedPasswordQualities();
+
+
+    /** Sets the quality of the current password. */
+    public void setCurrentPasswordQuality(int currentPasswordQuality) {
+        mCurrentPasswordQuality = currentPasswordQuality;
+    }
+
+    /** Gets whether the preference related to this controller is the current lock type. */
+    protected boolean isCurrentLock() {
+        for (int allowedQuality : allowedPasswordQualities()) {
+            if (mCurrentPasswordQuality == allowedQuality) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Sets the current password so it can be provided in the bundle in the fragment. */
+    public void setCurrentPassword(byte[] currentPassword) {
+        mCurrentPassword = currentPassword;
+    }
+
+    /** Gets the current password. */
+    protected byte[] getCurrentPassword() {
+        return mCurrentPassword;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(getSummary());
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        Fragment fragment = fragmentToOpen();
+        if (fragment != null) {
+            if (mCurrentPassword != null) {
+                Bundle args = fragment.getArguments();
+                if (args == null) {
+                    args = new Bundle();
+                }
+                args.putByteArray(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK, mCurrentPassword);
+                fragment.setArguments(args);
+            }
+            getFragmentController().launchFragment(fragment);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return mCarUserManagerHelper.isCurrentProcessGuestUser() ? DISABLED_FOR_USER : AVAILABLE;
+    }
+
+    private CharSequence getSummary() {
+        return isCurrentLock() ? getContext().getString(R.string.current_screen_lock) : "";
+    }
+}
diff --git a/src/com/android/car/settings/security/LockTypeDialogFragment.java b/src/com/android/car/settings/security/LockTypeDialogFragment.java
deleted file mode 100644
index e7e6386..0000000
--- a/src/com/android/car/settings/security/LockTypeDialogFragment.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.security;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-
-import androidx.car.app.CarListDialog;
-
-import com.android.car.settings.R;
-
-/**
- * Dialog that allows user to switch lock type.  The containing Activity must implement the
- * OnLockSelectListener interface
- */
-public class LockTypeDialogFragment extends DialogFragment {
-    // Please make sure the order is sync with LOCK_TYPES array
-    public static final int POSITION_NONE = 0;
-    public static final int POSITION_PIN = 1;
-    public static final int POSITION_PATTERN = 2;
-    public static final int POSITION_PASSWORD = 3;
-
-    private static final int[] LOCK_TYPES = {
-            R.string.security_lock_none,
-            R.string.security_lock_pin,
-            R.string.security_lock_pattern,
-            R.string.security_lock_password };
-
-    private OnLockSelectListener mOnLockSelectListener;
-
-    @Override
-    public void onAttach(Context context) {
-        super.onAttach(context);
-
-        if (!(getActivity() instanceof OnLockSelectListener)) {
-            throw new RuntimeException("Parent must implement OnLockSelectListener");
-        }
-
-        mOnLockSelectListener = (OnLockSelectListener) getActivity();
-    }
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        String[] items = new String[LOCK_TYPES.length];
-        for (int i = 0; i < LOCK_TYPES.length; ++i) {
-            items[i] = getResources().getString(LOCK_TYPES[i]);
-        }
-
-        return new CarListDialog.Builder(getContext())
-                .setItems(items, (dialog, pos) -> mOnLockSelectListener.onLockTypeSelected(pos))
-                .create();
-    }
-
-    /**
-     * Handles when a lock type is selected
-     */
-    interface OnLockSelectListener {
-        void onLockTypeSelected(int position);
-    }
-}
diff --git a/src/com/android/car/settings/security/NoLockPreferenceController.java b/src/com/android/car/settings/security/NoLockPreferenceController.java
new file mode 100644
index 0000000..316d43c
--- /dev/null
+++ b/src/com/android/car/settings/security/NoLockPreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.internal.widget.LockPatternUtils;
+
+/** Business logic for the no lock preference. */
+public class NoLockPreferenceController extends LockTypeBasePreferenceController {
+
+    private static final int[] ALLOWED_PASSWORD_QUALITIES =
+            new int[]{DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED};
+
+    @VisibleForTesting
+    final ConfirmRemoveScreenLockDialog.ConfirmRemoveScreenLockListener mRemoveLockListener =
+            () -> {
+                int userId = new CarUserManagerHelper(getContext()).getCurrentProcessUserId();
+                new LockPatternUtils(getContext()).clearLock(getCurrentPassword(), userId);
+                getFragmentController().goBack();
+            };
+
+    public NoLockPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+
+    /**
+     * If the dialog to confirm removal of lock was open previously, make sure the listener is
+     * restored.
+     */
+    @Override
+    protected void onCreateInternal() {
+        ConfirmRemoveScreenLockDialog dialog =
+                (ConfirmRemoveScreenLockDialog) getFragmentController().findDialogByTag(
+                        ConfirmRemoveScreenLockDialog.TAG);
+        if (dialog != null) {
+            dialog.setConfirmRemoveScreenLockListener(mRemoveLockListener);
+        }
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        ConfirmRemoveScreenLockDialog dialog = new ConfirmRemoveScreenLockDialog();
+        dialog.setConfirmRemoveScreenLockListener(mRemoveLockListener);
+        getFragmentController().showDialog(dialog,
+                ConfirmRemoveScreenLockDialog.TAG);
+        return true;
+    }
+
+    @Override
+    protected Fragment fragmentToOpen() {
+        // Selecting this preference does not open a new fragment. Instead it opens a dialog to
+        // confirm the removal of the existing lock screen.
+        return null;
+    }
+
+    @Override
+    protected int[] allowedPasswordQualities() {
+        return ALLOWED_PASSWORD_QUALITIES;
+    }
+}
diff --git a/src/com/android/car/settings/security/PasswordHelper.java b/src/com/android/car/settings/security/PasswordHelper.java
index 1e7a41c..daefc84 100644
--- a/src/com/android/car/settings/security/PasswordHelper.java
+++ b/src/com/android/car/settings/security/PasswordHelper.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 
 import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -30,27 +32,20 @@
  */
 public class PasswordHelper {
     public static final String EXTRA_CURRENT_SCREEN_LOCK = "extra_current_screen_lock";
-
-    /**
-     * Allow non-control Latin-1 characters only.
-     */
-    private static final String VALID_CHAR_PATTERN = "^[\\x20-\\x7F ]*$";
-
     /**
      * Required minimum length of PIN or password.
      */
-    static final int MIN_LENGTH = 4;
-
+    public static final int MIN_LENGTH = 4;
     // Error code returned from validate(String).
     static final int NO_ERROR = 0;
     static final int CONTAINS_INVALID_CHARACTERS = 1;
     static final int TOO_SHORT = 1 << 1;
     static final int CONTAINS_NON_DIGITS = 1 << 2;
     static final int CONTAINS_SEQUENTIAL_DIGITS = 1 << 3;
-
+    private static final Logger LOG = new Logger(PasswordHelper.class);
     private final boolean mIsPin;
 
-    PasswordHelper(boolean isPin) {
+    public PasswordHelper(boolean isPin) {
         mIsPin = isPin;
     }
 
@@ -66,43 +61,80 @@
     /**
      * Validates PIN/Password and returns the validation result.
      *
-     * @param password the raw password the user typed in
+     * @param password the raw bytes for the password the user typed in
      * @return the error code which should be non-zero where there is error. Otherwise
      * {@link #NO_ERROR} should be returned.
      */
-    public int validate(String password) {
+    public int validate(byte[] password) {
         return mIsPin ? validatePin(password) : validatePassword(password);
     }
 
     /**
+     * Validates PIN/Password using the setup wizard {@link ValidateLockFlags}.
+     *
+     * @param password The password to validate.
+     * @return The error code where 0 is no error.
+     */
+    public int validateSetupWizard(byte[] password) {
+        return mIsPin ? translateSettingsToSuwError(validatePin(password))
+                : translateSettingsToSuwError(validatePassword(password));
+    }
+
+    /**
      * Converts error code from validatePassword to an array of messages describing the errors with
      * important message comes first.  The messages are concatenated with a space in between.
      * Please make sure each message ends with a period.
-     * @param errorCode the code returned by {@link #validatePassword(String) validatePassword}
+     *
+     * @param errorCode the code returned by {@link #validatePassword(byte[]) validatePassword}
      */
     public List<String> convertErrorCodeToMessages(Context context, int errorCode) {
         return mIsPin ? convertPinErrorCodeToMessages(context, errorCode) :
                 convertPasswordErrorCodeToMessages(context, errorCode);
     }
 
-    private int validatePassword(String password) {
+    private int validatePassword(byte[] password) {
         int errorCode = NO_ERROR;
 
-        if (password.length() < MIN_LENGTH) {
+        if (password.length < MIN_LENGTH) {
             errorCode |= TOO_SHORT;
         }
 
-        if (!password.matches(VALID_CHAR_PATTERN)) {
-            errorCode |= CONTAINS_INVALID_CHARACTERS;
+        // Allow non-control Latin-1 characters only.
+        for (int i = 0; i < password.length; i++) {
+            char c = (char) password[i];
+            if (c < 32 || c > 127) {
+                errorCode |= CONTAINS_INVALID_CHARACTERS;
+                break;
+            }
         }
 
         return errorCode;
     }
 
-    private int validatePin(String pin) {
+    private int translateSettingsToSuwError(int error) {
+        int output = 0;
+        if ((error & CONTAINS_NON_DIGITS) > 0) {
+            LOG.v("CONTAINS_NON_DIGITS");
+            output |= ValidateLockFlags.INVALID_BAD_SYMBOLS;
+        }
+        if ((error & CONTAINS_INVALID_CHARACTERS) > 0) {
+            LOG.v("INVALID_CHAR");
+            output |= ValidateLockFlags.INVALID_BAD_SYMBOLS;
+        }
+        if ((error & TOO_SHORT) > 0) {
+            LOG.v("TOO_SHORT");
+            output |= ValidateLockFlags.INVALID_LENGTH;
+        }
+        if ((error & CONTAINS_SEQUENTIAL_DIGITS) > 0) {
+            LOG.v("SEQUENTIAL_DIGITS");
+            output |= ValidateLockFlags.INVALID_LACKS_COMPLEXITY;
+        }
+        return output;
+    }
+
+    private int validatePin(byte[] pin) {
         int errorCode = NO_ERROR;
-        byte[] pinBytes = pin != null ? pin.getBytes() : null;
-        PasswordMetrics metrics = PasswordMetrics.computeForPassword(pinBytes);
+        PasswordMetrics metrics = PasswordMetrics.computeForPassword(pin);
         int passwordQuality = getPasswordQuality();
 
         if (metrics.length < MIN_LENGTH) {
@@ -115,7 +147,7 @@
 
         if (passwordQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX) {
             // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
-            int sequence = PasswordMetrics.maxLengthSequence(pinBytes);
+            int sequence = PasswordMetrics.maxLengthSequence(pin);
             if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) {
                 errorCode |= CONTAINS_SEQUENTIAL_DIGITS;
             }
diff --git a/src/com/android/car/settings/security/PasswordLockPreferenceController.java b/src/com/android/car/settings/security/PasswordLockPreferenceController.java
new file mode 100644
index 0000000..77d105c
--- /dev/null
+++ b/src/com/android/car/settings/security/PasswordLockPreferenceController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Business logic for the lock password picker preference. */
+public class PasswordLockPreferenceController extends LockTypeBasePreferenceController {
+
+    private static final int[] ALLOWED_PASSWORD_QUALITIES = new int[]{
+            DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
+            DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+    };
+
+    public PasswordLockPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Fragment fragmentToOpen() {
+        return ChooseLockPinPasswordFragment.newPasswordInstance();
+    }
+
+    @Override
+    protected int[] allowedPasswordQualities() {
+        return ALLOWED_PASSWORD_QUALITIES;
+    }
+}
diff --git a/src/com/android/car/settings/security/PatternLockPreferenceController.java b/src/com/android/car/settings/security/PatternLockPreferenceController.java
new file mode 100644
index 0000000..1266bd7
--- /dev/null
+++ b/src/com/android/car/settings/security/PatternLockPreferenceController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Business logic for the lock pattern picker preference. */
+public class PatternLockPreferenceController extends LockTypeBasePreferenceController {
+
+    private static final int[] ALLOWED_PASSWORD_QUALITIES =
+            new int[]{DevicePolicyManager.PASSWORD_QUALITY_SOMETHING};
+
+    public PatternLockPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Fragment fragmentToOpen() {
+        return ChooseLockPatternFragment.newInstance();
+    }
+
+    @Override
+    protected int[] allowedPasswordQualities() {
+        return ALLOWED_PASSWORD_QUALITIES;
+    }
+}
diff --git a/src/com/android/car/settings/security/PinLockPreferenceController.java b/src/com/android/car/settings/security/PinLockPreferenceController.java
new file mode 100644
index 0000000..d8169c0
--- /dev/null
+++ b/src/com/android/car/settings/security/PinLockPreferenceController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Business logic for the lock pin picker preference. */
+public class PinLockPreferenceController extends LockTypeBasePreferenceController {
+
+    private static final int[] ALLOWED_PASSWORD_QUALITIES = new int[]{
+            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
+    };
+
+    public PinLockPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Fragment fragmentToOpen() {
+        return ChooseLockPinPasswordFragment.newPinInstance();
+    }
+
+    @Override
+    protected int[] allowedPasswordQualities() {
+        return ALLOWED_PASSWORD_QUALITIES;
+    }
+}
diff --git a/src/com/android/car/settings/security/PinPadView.java b/src/com/android/car/settings/security/PinPadView.java
index 452ca8c..ee7b5b1 100644
--- a/src/com/android/car/settings/security/PinPadView.java
+++ b/src/com/android/car/settings/security/PinPadView.java
@@ -16,13 +16,9 @@
 
 package com.android.car.settings.security;
 
-import android.annotation.DrawableRes;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.VisibleForTesting;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -31,6 +27,10 @@
 import android.widget.ImageButton;
 import android.widget.TextView;
 
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.settings.R;
 
 import java.util.ArrayList;
@@ -41,12 +41,12 @@
  */
 public class PinPadView extends GridLayout {
     // Number of keys in the pin pad, 0-9 plus backspace and enter keys.
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting
     static final int NUM_KEYS = 12;
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    static final int[] PIN_PAD_DIGIT_KEYS = { R.id.key0, R.id.key1, R.id.key2, R.id.key3,
-            R.id.key4, R.id.key5, R.id.key6, R.id.key7, R.id.key8, R.id.key9 };
+    @VisibleForTesting
+    static final int[] PIN_PAD_DIGIT_KEYS = {R.id.key0, R.id.key1, R.id.key2, R.id.key3,
+            R.id.key4, R.id.key5, R.id.key6, R.id.key7, R.id.key8, R.id.key9};
 
     /**
      * The delay in milliseconds between character deletion when the user continuously holds the
@@ -55,15 +55,18 @@
     private static final int LONG_CLICK_DELAY_MILLS = 100;
 
     private final List<View> mPinKeys = new ArrayList<>(NUM_KEYS);
-    private PinPadClickListener mOnClickListener;
-    private ImageButton mEnterKey;
-    private Runnable mOnBackspaceLongClick = new Runnable() {
+    private final Runnable mOnBackspaceLongClick = new Runnable() {
         public void run() {
-            mOnClickListener.onBackspaceClick();
-            getHandler().postDelayed(this, LONG_CLICK_DELAY_MILLS);
+            if (mOnClickListener != null) {
+                mOnClickListener.onBackspaceClick();
+                getHandler().postDelayed(this, LONG_CLICK_DELAY_MILLS);
+            }
         }
     };
 
+    private PinPadClickListener mOnClickListener;
+    private ImageButton mEnterKey;
+
     public PinPadView(Context context) {
         super(context);
         init(null, 0, 0);
@@ -97,7 +100,7 @@
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
-        for (View key: mPinKeys) {
+        for (View key : mPinKeys) {
             key.setEnabled(enabled);
         }
     }
@@ -105,7 +108,7 @@
     /**
      * Set the resource Id of the enter key icon.
      *
-     * @param drawableId  The resource Id of the drawable.
+     * @param drawableId The resource Id of the drawable.
      */
     public void setEnterKeyIcon(@DrawableRes int drawableId) {
         mEnterKey.setImageResource(drawableId);
@@ -129,42 +132,40 @@
 
     private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         LayoutInflater inflater = LayoutInflater.from(getContext());
-        inflater.inflate(R.layout.pin_pad_view, this, true);
+        TypedArray typedArray = getContext().obtainStyledAttributes(
+                attrs, R.styleable.PinPadView, defStyleAttr, defStyleRes);
+        inflater.inflate(
+                typedArray.getResourceId(R.styleable.PinPadView_layout, R.layout.pin_pad_view),
+                this, true);
+        typedArray.recycle();
 
         for (int keyId : PIN_PAD_DIGIT_KEYS) {
-            TextView key = (TextView) findViewById(keyId);
+            TextView key = findViewById(keyId);
             String digit = key.getTag().toString();
             key.setOnClickListener(v -> mOnClickListener.onDigitKeyClick(digit));
             mPinKeys.add(key);
         }
 
-        View backspace = findViewById(R.id.key_backspace);
+        ImageButton backspace = findViewById(R.id.key_backspace);
         backspace.setOnTouchListener((v, event) -> {
             switch (event.getAction()) {
                 case MotionEvent.ACTION_DOWN:
                     getHandler().post(mOnBackspaceLongClick);
-                    return true;
+                    // Must return false so that ripple can show
+                    return false;
                 case MotionEvent.ACTION_UP:
                     getHandler().removeCallbacks(mOnBackspaceLongClick);
-                    return true;
+                    // Must return false so that ripple can show
+                    return false;
                 default:
                     return false;
             }
         });
         mPinKeys.add(backspace);
 
-        mEnterKey = (ImageButton) findViewById(R.id.key_enter);
-
-        TypedArray typedArray = getContext().obtainStyledAttributes(
-                attrs, R.styleable.PinPadView, defStyleAttr, defStyleRes);
-        Drawable enterKeyDrawable = typedArray.getDrawable(R.styleable.PinPadView_enterKeyDrawable);
-        typedArray.recycle();
-
-        if (enterKeyDrawable != null) {
-            mEnterKey.setImageDrawable(enterKeyDrawable);
-        }
-
+        mEnterKey = findViewById(R.id.key_enter);
         mEnterKey.setOnClickListener(v -> mOnClickListener.onEnterKeyClick());
+
         mPinKeys.add(mEnterKey);
     }
 
diff --git a/src/com/android/car/settings/security/SaveLockWorkerBase.java b/src/com/android/car/settings/security/SaveLockWorkerBase.java
index a3504e2..8fdcb64 100644
--- a/src/com/android/car/settings/security/SaveLockWorkerBase.java
+++ b/src/com/android/car/settings/security/SaveLockWorkerBase.java
@@ -16,11 +16,12 @@
 
 package com.android.car.settings.security;
 
-import android.annotation.WorkerThread;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.app.Fragment;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.fragment.app.Fragment;
 
 import com.android.car.settings.common.Logger;
 import com.android.internal.widget.LockPatternUtils;
@@ -88,7 +89,7 @@
         }
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting
     boolean saveAndVerifyInBackground(Void... params) {
         boolean isSaveSuccessful = true;
 
diff --git a/src/com/android/car/settings/security/SavePasswordWorker.java b/src/com/android/car/settings/security/SavePasswordWorker.java
index 06886fa..61862fd 100644
--- a/src/com/android/car/settings/security/SavePasswordWorker.java
+++ b/src/com/android/car/settings/security/SavePasswordWorker.java
@@ -23,11 +23,11 @@
 
 public class SavePasswordWorker extends SaveLockWorkerBase {
 
-    private String mEnteredPassword;
-    private String mCurrentPassword;
+    private byte[] mEnteredPassword;
+    private byte[] mCurrentPassword;
     private int mRequestedQuality;
 
-    void start(int userId, String enteredPassword, String currentPassword, int requestedQuality) {
+    void start(int userId, byte[] enteredPassword, byte[] currentPassword, int requestedQuality) {
         init(userId);
         mEnteredPassword = enteredPassword;
         mCurrentPassword = currentPassword;
diff --git a/src/com/android/car/settings/security/SavePatternWorker.java b/src/com/android/car/settings/security/SavePatternWorker.java
index fe0fbf8..bfd7b27 100644
--- a/src/com/android/car/settings/security/SavePatternWorker.java
+++ b/src/com/android/car/settings/security/SavePatternWorker.java
@@ -26,9 +26,9 @@
 public class SavePatternWorker extends SaveLockWorkerBase {
 
     private List<LockPatternView.Cell> mChosenPattern;
-    private String mCurrentPattern;
+    private byte[] mCurrentPattern;
 
-    void start(int userId, List<LockPatternView.Cell> chosenPattern, String currentPattern) {
+    void start(int userId, List<LockPatternView.Cell> chosenPattern, byte[] currentPattern) {
         init(userId);
         mCurrentPattern = currentPattern;
         mChosenPattern = chosenPattern;
@@ -41,8 +41,7 @@
         // If called after saveLockPattern, this will always be true
         boolean isPatternEverChosen = getUtils().isPatternEverChosen(userId);
 
-        byte[] currentPatternBytes = mCurrentPattern != null ? mCurrentPattern.getBytes() : null;
-        getUtils().saveLockPattern(mChosenPattern, currentPatternBytes, userId);
+        getUtils().saveLockPattern(mChosenPattern, mCurrentPattern, userId);
 
         if (!isPatternEverChosen) {
             getUtils().setVisiblePatternEnabled(true, userId);
diff --git a/src/com/android/car/settings/security/SecurityEntryPreferenceController.java b/src/com/android/car/settings/security/SecurityEntryPreferenceController.java
new file mode 100644
index 0000000..b39c1f7
--- /dev/null
+++ b/src/com/android/car/settings/security/SecurityEntryPreferenceController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller which determines if the top level entry into Security settings should be displayed
+ * based on the user status.
+ */
+public class SecurityEntryPreferenceController extends PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public SecurityEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return mCarUserManagerHelper.isCurrentProcessGuestUser() ? DISABLED_FOR_USER : AVAILABLE;
+    }
+}
diff --git a/src/com/android/car/settings/security/SettingsScreenLockActivity.java b/src/com/android/car/settings/security/SettingsScreenLockActivity.java
index a608545..d995ed3 100644
--- a/src/com/android/car/settings/security/SettingsScreenLockActivity.java
+++ b/src/com/android/car/settings/security/SettingsScreenLockActivity.java
@@ -20,80 +20,72 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.CarSettingActivity;
+import com.android.car.settings.common.BaseCarSettingsActivity;
 import com.android.car.settings.common.Logger;
 import com.android.internal.widget.LockPatternUtils;
 
 /**
  * Activity for setting screen locks
  */
-public class SettingsScreenLockActivity extends CarSettingActivity implements CheckLockListener {
+public class SettingsScreenLockActivity extends BaseCarSettingsActivity implements
+        CheckLockListener {
 
     private static final Logger LOG = new Logger(SettingsScreenLockActivity.class);
 
     private int mPasswordQuality;
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
+    @Nullable
+    protected Fragment getInitialFragment() {
         mPasswordQuality = new LockPatternUtils(this).getKeyguardStoredPasswordQuality(
                 UserHandle.myUserId());
 
-        if (savedInstanceState == null) {
-            BaseFragment fragment;
-            switch (mPasswordQuality) {
-                case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
-                    fragment = ChooseLockTypeFragment.newInstance();
-                    break;
-                case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
-                    fragment = ConfirmLockPatternFragment.newInstance(
-                            /* isInSetupWizard= */ false);
-                    break;
-                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
-                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
-                    fragment = ConfirmLockPinPasswordFragment.newPinInstance(
-                            /* isInSetupWizard= */ false);
-                    break;
-                case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
-                case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
-                    fragment = ConfirmLockPinPasswordFragment.newPasswordInstance(
-                            /* isInSetupWizard= */ false);
-                    break;
-                default:
-                    LOG.e("Unexpected password quality: " + String.valueOf(mPasswordQuality));
-                    fragment = ConfirmLockPinPasswordFragment.newPasswordInstance(
-                            /* isInSetupWizard= */ false);
-            }
-
-            Bundle bundle = fragment.getArguments();
-            if (bundle == null) {
-                bundle = new Bundle();
-            }
-            bundle.putInt(ChooseLockTypeFragment.EXTRA_CURRENT_PASSWORD_QUALITY, mPasswordQuality);
-            fragment.setArguments(bundle);
-
-            getSupportFragmentManager()
-                    .beginTransaction()
-                    .add(R.id.fragment_container, fragment)
-                    .addToBackStack(null)
-                    .commit();
+        Fragment fragment;
+        switch (mPasswordQuality) {
+            case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
+                fragment = new ChooseLockTypeFragment();
+                break;
+            case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+                fragment = new ConfirmLockPatternFragment();
+                break;
+            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+                fragment = ConfirmLockPinPasswordFragment.newPinInstance();
+                break;
+            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+                fragment = ConfirmLockPinPasswordFragment.newPasswordInstance();
+                break;
+            default:
+                LOG.e("Unexpected password quality: " + String.valueOf(mPasswordQuality));
+                fragment = ConfirmLockPinPasswordFragment.newPasswordInstance();
         }
-    }
 
-    @Override
-    public void onLockVerified(String lock) {
-        BaseFragment fragment = ChooseLockTypeFragment.newInstance();
         Bundle bundle = fragment.getArguments();
         if (bundle == null) {
             bundle = new Bundle();
         }
-        bundle.putString(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK, lock);
+        bundle.putInt(ChooseLockTypeFragment.EXTRA_CURRENT_PASSWORD_QUALITY, mPasswordQuality);
+        fragment.setArguments(bundle);
+        return fragment;
+    }
+
+    @Override
+    public void onLockVerified(byte[] lock) {
+        Fragment fragment = new ChooseLockTypeFragment();
+        Bundle bundle = fragment.getArguments();
+        if (bundle == null) {
+            bundle = new Bundle();
+        }
+        bundle.putByteArray(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK, lock);
         bundle.putInt(ChooseLockTypeFragment.EXTRA_CURRENT_PASSWORD_QUALITY, mPasswordQuality);
         fragment.setArguments(bundle);
 
+        // Intentionally not using launchFragment(), since we do not want to add to the back stack.
         getSupportFragmentManager()
                 .beginTransaction()
                 .replace(R.id.fragment_container, fragment)
diff --git a/src/com/android/car/settings/security/SetupWizardScreenLockActivity.java b/src/com/android/car/settings/security/SetupWizardScreenLockActivity.java
deleted file mode 100644
index 4a11eaf..0000000
--- a/src/com/android/car/settings/security/SetupWizardScreenLockActivity.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.security;
-
-import android.app.admin.DevicePolicyManager;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.support.v7.widget.Toolbar;
-import android.text.TextUtils;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.CarSettingActivity;
-import com.android.car.settings.common.Logger;
-import com.android.car.settingslib.util.ResultCodes;
-import com.android.internal.widget.LockPatternUtils;
-
-/**
- * Entry point Activity for Setup Wizard to set screen lock.
- */
-public class SetupWizardScreenLockActivity extends CarSettingActivity implements
-        CheckLockListener,
-        LockTypeDialogFragment.OnLockSelectListener {
-
-    private static final Logger LOG = new Logger(SetupWizardScreenLockActivity.class);
-
-    private String mCurrLock;
-    private int mPasswordQuality;
-
-    @Override
-    public void launchFragment(BaseFragment fragment) {
-        Bundle args = fragment.getArguments();
-        if (args == null) {
-            args = new Bundle();
-        }
-        args.putBoolean(BaseFragment.EXTRA_RUNNING_IN_SETUP_WIZARD, true);
-        if (!TextUtils.isEmpty(mCurrLock)) {
-            args.putString(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK, mCurrLock);
-        }
-        fragment.setArguments(args);
-
-        getSupportFragmentManager()
-                .beginTransaction()
-                .replace(R.id.fragment_container, fragment)
-                .commit();
-    }
-
-    @Override
-    public void goBack() {
-        setResult(RESULT_CANCELED);
-        finish();
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.suw_activity);
-        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
-        setSupportActionBar(toolbar);
-
-        mPasswordQuality = new LockPatternUtils(this).getKeyguardStoredPasswordQuality(
-                UserHandle.myUserId());
-
-        if (savedInstanceState == null) {
-            BaseFragment fragment;
-            switch (mPasswordQuality) {
-                case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
-                    // In Setup Wizard, the landing page is always the Pin screen
-                    fragment = ChooseLockPinPasswordFragment.newPinInstance(
-                            /* isInSetupWizard= */ true);
-                    break;
-                case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
-                    fragment = ConfirmLockPatternFragment.newInstance(
-                            /* isInSetupWizard= */ true);
-                    break;
-                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: // Fall through
-                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
-                    fragment = ConfirmLockPinPasswordFragment.newPinInstance(
-                            /* isInSetupWizard= */ true);
-                    break;
-                case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: // Fall through
-                case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
-                    fragment = ConfirmLockPinPasswordFragment.newPasswordInstance(
-                            /* isInSetupWizard= */ true);
-                    break;
-                default:
-                    LOG.e("Unexpected password quality: " + String.valueOf(mPasswordQuality));
-                    fragment = ConfirmLockPinPasswordFragment.newPasswordInstance(
-                            /* isInSetupWizard= */ true);
-            }
-
-            launchFragment(fragment);
-        }
-    }
-
-    /**
-     * Handler that will be invoked when Cancel button is clicked in the fragment.
-     */
-    public void onCancel() {
-        setResult(ResultCodes.RESULT_SKIP);
-        finish();
-    }
-
-    /**
-     * Handler that will be invoked when lock save is completed.
-     */
-    public void onComplete() {
-        setResult(RESULT_OK);
-        finish();
-    }
-
-    @Override
-    public void onLockVerified(String lock) {
-        mCurrLock = lock;
-        // In Setup Wizard, the landing page is always the Pin screen
-        BaseFragment fragment = ChooseLockPinPasswordFragment.newPinInstance(
-                /* isInSetupWizard= */ true);
-        launchFragment(fragment);
-    }
-
-    @Override
-    public void onLockTypeSelected(int position) {
-        BaseFragment fragment = null;
-
-        switch(position) {
-            case LockTypeDialogFragment.POSITION_NONE:
-                if (mPasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-                    byte[] currLockBytes = mCurrLock != null ? mCurrLock.getBytes() : null;
-                    new LockPatternUtils(this).clearLock(currLockBytes, UserHandle.myUserId());
-                }
-                setResult(ResultCodes.RESULT_NONE);
-                finish();
-                break;
-            case LockTypeDialogFragment.POSITION_PIN:
-                fragment = ChooseLockPinPasswordFragment.newPinInstance(
-                        /* isInSetupWizard= */ true);
-                break;
-            case LockTypeDialogFragment.POSITION_PATTERN:
-                fragment = ChooseLockPatternFragment.newInstance(/* isInSetupWizard= */ true);
-                break;
-            case LockTypeDialogFragment.POSITION_PASSWORD:
-                fragment = ChooseLockPinPasswordFragment.newPasswordInstance(
-                        /* isInSetupWizard= */ true);
-                break;
-            default:
-                LOG.e("Lock type position out of bounds");
-        }
-        if (fragment != null) {
-            launchFragment(fragment);
-        }
-    }
-}
diff --git a/src/com/android/car/settings/setupservice/InitialLockSetupService.java b/src/com/android/car/settings/setupservice/InitialLockSetupService.java
new file mode 100644
index 0000000..7ea8bc8
--- /dev/null
+++ b/src/com/android/car/settings/setupservice/InitialLockSetupService.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.setupservice;
+
+
+import android.app.Service;
+import android.app.admin.DevicePolicyManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.security.PasswordHelper;
+import com.android.car.setupwizardlib.IInitialLockSetupService;
+import com.android.car.setupwizardlib.InitialLockSetupConstants;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.LockTypes;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.SetLockCodes;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
+import com.android.car.setupwizardlib.InitialLockSetupHelper;
+import com.android.car.setupwizardlib.LockConfig;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Service that is used by Setup Wizard (exclusively) to set the initial lock screen.
+ *
+ * <p>This service provides functionality to get the lock config state, check if a password is
+ * valid based on the Settings defined password criteria, and save a lock if there is not one
+ * already saved. The interface for these operations is found in the {@link
+ * IInitialLockSetupService}.
+ */
+public class InitialLockSetupService extends Service {
+
+    private static final Logger LOG = new Logger(InitialLockSetupService.class);
+    private static final String SET_LOCK_PERMISSION = "com.android.car.settings.SET_INITIAL_LOCK";
+
+    private final InitialLockSetupServiceImpl mIInitialLockSetupService =
+            new InitialLockSetupServiceImpl();
+
+    /**
+     * Will return an {@link IBinder} for the service unless either the caller does not have the
+     * appropriate permissions or a lock has already been set on the device. In this case, the
+     * service will return {@code null}.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        LOG.v("onBind");
+        if (checkCallingOrSelfPermission(SET_LOCK_PERMISSION)
+                != PackageManager.PERMISSION_GRANTED) {
+            // Check permission as a failsafe.
+            return null;
+        }
+        int userId = new CarUserManagerHelper(getApplicationContext()).getCurrentProcessUserId();
+        LockPatternUtils lockPatternUtils = new LockPatternUtils(getApplicationContext());
+        // Deny binding if there is an existing lock.
+        if (lockPatternUtils.getKeyguardStoredPasswordQuality(userId)
+                != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+            LOG.v("Rejecting binding, lock exists");
+            return null;
+        }
+        return mIInitialLockSetupService;
+    }
+
+    // Translates the byte[] pattern received into the List<LockPatternView.Cell> that is
+    // recognized by LockPatternUtils.
+    private List<LockPatternView.Cell> toSettingsPattern(byte[] pattern) {
+        List<LockPatternView.Cell> outputList = new ArrayList<>();
+        for (int i = 0; i < pattern.length; i++) {
+            outputList.add(LockPatternView.Cell.of(
+                    InitialLockSetupHelper.getPatternCellRowFromByte(pattern[i]),
+                    InitialLockSetupHelper.getPatternCellColumnFromByte(pattern[i])));
+        }
+        return outputList;
+    }
+
+    // Implementation of the service binder interface.
+    private class InitialLockSetupServiceImpl extends IInitialLockSetupService.Stub {
+
+        @Override
+        public int getServiceVersion() {
+            return InitialLockSetupConstants.LIBRARY_VERSION;
+        }
+
+        @Override
+        public LockConfig getLockConfig(@LockTypes int lockType) {
+            // All lock types currently are configured the same.
+            switch (lockType) {
+                case LockTypes.PASSWORD:
+                    // fall through
+                case LockTypes.PIN:
+                    // fall through
+                case LockTypes.PATTERN:
+                    return new LockConfig(/* enabled= */true, PasswordHelper.MIN_LENGTH);
+            }
+            return null;
+        }
+
+        @Override
+        @ValidateLockFlags
+        public int checkValidLock(@LockTypes int lockType, byte[] password) {
+            PasswordHelper passwordHelper;
+            switch (lockType) {
+                case LockTypes.PASSWORD:
+                    passwordHelper = new PasswordHelper(/* isPin= */ false);
+                    return passwordHelper.validateSetupWizard(password);
+                case LockTypes.PIN:
+                    passwordHelper = new PasswordHelper(/* isPin= */ true);
+                    return passwordHelper.validateSetupWizard(password);
+                case LockTypes.PATTERN:
+                    return password.length >= LockPatternUtils.MIN_LOCK_PATTERN_SIZE
+                            ? 0 : ValidateLockFlags.INVALID_LENGTH;
+                default:
+                    LOG.e("other lock type, returning generic error");
+                    return ValidateLockFlags.INVALID_GENERIC;
+            }
+        }
+
+        @Override
+        @SetLockCodes
+        public int setLock(@LockTypes int lockType, byte[] password) {
+            int userId = new CarUserManagerHelper(
+                    InitialLockSetupService.this.getApplicationContext())
+                    .getCurrentProcessUserId();
+            LockPatternUtils lockPatternUtils = new LockPatternUtils(
+                    InitialLockSetupService.this.getApplicationContext());
+            int currentPassword = lockPatternUtils.getKeyguardStoredPasswordQuality(userId);
+            if (currentPassword != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+                LOG.v("Password already set, rejecting call to setLock");
+                return SetLockCodes.FAIL_LOCK_EXISTS;
+            }
+            if (!InitialLockSetupHelper.isValidLockResultCode(checkValidLock(lockType, password))) {
+                LOG.v("Password is not valid, rejecting call to setLock");
+                return SetLockCodes.FAIL_LOCK_INVALID;
+            }
+
+            boolean success = false;
+            try {
+                switch (lockType) {
+                    case LockTypes.PASSWORD:
+                        // Need to remove setup wizard lib byte array encoding and use the
+                        // LockPatternUtils encoding.
+                        byte[] encodedPassword = LockPatternUtils.charSequenceToByteArray(
+                                InitialLockSetupHelper.byteArrayToCharSequence(password));
+                        lockPatternUtils.saveLockPassword(encodedPassword,
+                                /* savedPassword= */ null,
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userId);
+                        success = true;
+                        break;
+                    case LockTypes.PIN:
+                        // Need to remove setup wizard lib byte array encoding and use the
+                        // LockPatternUtils encoding.
+                        byte[] encodedPin = LockPatternUtils.charSequenceToByteArray(
+                                InitialLockSetupHelper.byteArrayToCharSequence(password));
+                        lockPatternUtils.saveLockPassword(encodedPin, /* savedPassword= */ null,
+                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, userId);
+                        success = true;
+                        break;
+                    case LockTypes.PATTERN:
+                        // Need to remove the setup wizard lib pattern encoding and use the
+                        // LockPatternUtils pattern format.
+                        List<LockPatternView.Cell> pattern = toSettingsPattern(password);
+                        lockPatternUtils.saveLockPattern(pattern, userId);
+                        pattern.clear();
+                        success = true;
+                        break;
+                    default:
+                        LOG.e("Unknown lock type, returning a failure");
+                }
+            } catch (Exception e) {
+                LOG.e("Save lock exception", e);
+                success = false;
+            }
+            Arrays.fill(password, (byte) 0);
+            return success ? SetLockCodes.SUCCESS : SetLockCodes.FAIL_LOCK_GENERIC;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/sound/RingtonePreference.java b/src/com/android/car/settings/sound/RingtonePreference.java
new file mode 100644
index 0000000..e021174
--- /dev/null
+++ b/src/com/android/car/settings/sound/RingtonePreference.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 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.car.settings.sound;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.media.RingtoneManager;
+import android.util.AttributeSet;
+
+import androidx.preference.Preference;
+
+/**
+ * A {@link Preference} which extracts relevant ringtone attributes from XML. When used in
+ * conjunction with {@link RingtonePreferenceController}, it can be used to select a default
+ * ringtone for a given ringtone type.
+ *
+ * @attr ref android.R.styleable#RingtonePreference_ringtoneType
+ * @attr ref android.R.styleable#RingtonePreference_showSilent
+ */
+public class RingtonePreference extends Preference {
+
+    private boolean mShowSilent;
+    private int mRingtoneType;
+
+    public RingtonePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.RingtonePreference, 0, 0);
+        mRingtoneType = a.getInt(com.android.internal.R.styleable.RingtonePreference_ringtoneType,
+                RingtoneManager.TYPE_RINGTONE);
+        mShowSilent = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showSilent,
+                true);
+        setIntent(new Intent(RingtoneManager.ACTION_RINGTONE_PICKER));
+        a.recycle();
+    }
+
+    /**
+     * Returns the sound type(s) that are shown in the picker.
+     *
+     * @see #setRingtoneType(int)
+     */
+    public int getRingtoneType() {
+        return mRingtoneType;
+    }
+
+    /**
+     * Sets the sound type(s) that are shown in the picker.
+     *
+     * @param type The sound type(s) that are shown in the picker.
+     * @see RingtoneManager#EXTRA_RINGTONE_TYPE
+     */
+    public void setRingtoneType(int type) {
+        mRingtoneType = type;
+    }
+
+    /**
+     * Returns whether to a show an item for 'Silent'.
+     */
+    public boolean getShowSilent() {
+        return mShowSilent;
+    }
+
+    /**
+     * Sets whether to show an item for 'Silent'.
+     *
+     * @param showSilent Whether to show 'Silent'.
+     * @see RingtoneManager#EXTRA_RINGTONE_SHOW_SILENT
+     */
+    public void setShowSilent(boolean showSilent) {
+        mShowSilent = showSilent;
+    }
+}
diff --git a/src/com/android/car/settings/sound/RingtonePreferenceController.java b/src/com/android/car/settings/sound/RingtonePreferenceController.java
new file mode 100644
index 0000000..7a90b26
--- /dev/null
+++ b/src/com/android/car/settings/sound/RingtonePreferenceController.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 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.car.settings.sound;
+
+import android.app.Activity;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.common.ActivityResultCallback;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+/** Business logic for changing the default ringtone. */
+public class RingtonePreferenceController extends
+        PreferenceController<RingtonePreference> implements ActivityResultCallback {
+
+    private static final Logger LOG = new Logger(RingtonePreferenceController.class);
+    @VisibleForTesting
+    static final int REQUEST_CODE = 16;
+
+    // We use a user context so that default ringtones can differ per user.
+    private final Context mUserContext;
+
+    public RingtonePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mUserContext = createPackageContextAsUser(getContext(), UserHandle.myUserId());
+    }
+
+    @Override
+    protected Class<RingtonePreference> getPreferenceType() {
+        return RingtonePreference.class;
+    }
+
+    @Override
+    protected void updateState(RingtonePreference preference) {
+        Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(getContext(),
+                getPreference().getRingtoneType());
+        preference.setSummary(Ringtone.getTitle(getContext(), ringtoneUri, /* followSettingsUri= */
+                false, /* allowRemote= */ true));
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(RingtonePreference preference) {
+        onPrepareRingtonePickerIntent(preference, preference.getIntent());
+        getFragmentController().startActivityForResult(preference.getIntent(), REQUEST_CODE, this);
+        return true;
+    }
+
+    @Override
+    public void processActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        if (requestCode == REQUEST_CODE) {
+            if (resultCode != Activity.RESULT_OK || data == null) {
+                return;
+            }
+
+            Uri ringtoneUri = data.getParcelableExtra(
+                    RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+            RingtoneManager.setActualDefaultRingtoneUri(mUserContext,
+                    getPreference().getRingtoneType(), ringtoneUri);
+            refreshUi();
+        }
+    }
+
+    /**
+     * Prepares the intent to launch the ringtone picker. This can be modified
+     * to adjust the parameters of the ringtone picker.
+     */
+    private void onPrepareRingtonePickerIntent(RingtonePreference ringtonePreference,
+            Intent ringtonePickerIntent) {
+        Uri currentRingtone = RingtoneManager.getActualDefaultRingtoneUri(mUserContext,
+                ringtonePreference.getRingtoneType());
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,
+                currentRingtone);
+
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE,
+                ringtonePreference.getTitle());
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
+                ringtonePreference.getRingtoneType());
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT,
+                ringtonePreference.getShowSilent());
+
+        // Since we are picking the default ringtone, no need to show system default.
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
+        // Allow playback in external activity.
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
+    }
+
+    /**
+     * Returns a context created from the given context for the given user, or null if it fails.
+     */
+    private Context createPackageContextAsUser(Context context, int userId) {
+        try {
+            return context.createPackageContextAsUser(
+                    context.getPackageName(), 0 /* flags */, UserHandle.of(userId));
+        } catch (PackageManager.NameNotFoundException e) {
+            LOG.e("Failed to create user context", e);
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/sound/SoundSettingsFragment.java b/src/com/android/car/settings/sound/SoundSettingsFragment.java
index b1bf654..e04968c 100644
--- a/src/com/android/car/settings/sound/SoundSettingsFragment.java
+++ b/src/com/android/car/settings/sound/SoundSettingsFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -11,242 +11,22 @@
  * 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
+ * limitations under the License.
  */
+
 package com.android.car.settings.sound;
 
-import android.annotation.DrawableRes;
-import android.annotation.StringRes;
-import android.car.Car;
-import android.car.CarNotConnectedException;
-import android.car.media.CarAudioManager;
-import android.car.media.ICarVolumeCallback;
-import android.content.ComponentName;
-import android.content.ServiceConnection;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.media.AudioAttributes;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.util.Xml;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemAdapter;
-import androidx.car.widget.ListItemProvider.ListProvider;
-import androidx.car.widget.PagedListView;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
 
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Activity hosts sound related settings.
- */
-public class SoundSettingsFragment extends BaseFragment {
-    private static final Logger LOG = new Logger(SoundSettingsFragment.class);
-
-    private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
-    private static final String XML_TAG_VOLUME_ITEM = "item";
-
-    private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>();
-
-    private final List<ListItem> mVolumeLineItems = new ArrayList<>();
-
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
-                int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
-                cleanUpVolumeLineItems();
-                // Populates volume slider items from volume groups to UI.
-                for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
-                    final VolumeItem volumeItem = getVolumeItemForUsages(
-                            mCarAudioManager.getUsagesForVolumeGroupId(groupId));
-                    mVolumeLineItems.add(new VolumeLineItem(
-                            getContext(),
-                            mCarAudioManager,
-                            groupId,
-                            volumeItem.usage,
-                            volumeItem.icon,
-                            volumeItem.title));
-                }
-                updateList();
-                mCarAudioManager.registerVolumeCallback(mVolumeChangeCallback.asBinder());
-            } catch (CarNotConnectedException e) {
-                LOG.e("Car is not connected!", e);
-            }
-        }
-
-        /**
-         * This does not gets called when service is properly disconnected.
-         * So we need to also handle cleanups in onStop().
-         */
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            cleanupAudioManager();
-        }
-    };
-
-    private final ICarVolumeCallback mVolumeChangeCallback = new ICarVolumeCallback.Stub() {
-        @Override
-        public void onGroupVolumeChanged(int groupId, int flags) {
-            for (ListItem lineItem : mVolumeLineItems) {
-                VolumeLineItem volumeLineItem = (VolumeLineItem) lineItem;
-                if (volumeLineItem.getVolumeGroupId() == groupId) {
-                    volumeLineItem.updateProgress();
-                }
-            }
-            updateList();
-        }
-
-        @Override
-        public void onMasterMuteChanged(int flags) {
-            // ignored
-        }
-    };
-
-    private Car mCar;
-    private CarAudioManager mCarAudioManager;
-    private PagedListView mListView;
-    private ListItemAdapter mPagedListAdapter;
-
-    /**
-     * Creates a new instance of this fragment.
-     */
-    public static SoundSettingsFragment newInstance() {
-        SoundSettingsFragment soundSettingsFragment = new SoundSettingsFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.sound_settings);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.list);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar);
-        soundSettingsFragment.setArguments(bundle);
-        return soundSettingsFragment;
-    }
-
-    private void cleanupAudioManager() {
-        try {
-            mCarAudioManager.unregisterVolumeCallback(mVolumeChangeCallback.asBinder());
-        } catch (CarNotConnectedException e) {
-            LOG.e("Car is not connected!", e);
-        }
-        cleanUpVolumeLineItems();
-        mCarAudioManager = null;
-    }
-
-    private void updateList() {
-        if (getActivity() != null && mPagedListAdapter != null) {
-            getActivity().runOnUiThread(() -> mPagedListAdapter.notifyDataSetChanged());
-        }
-    }
+/** Fragment which shows the settings for sounds. */
+public class SoundSettingsFragment extends SettingsFragment {
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        loadAudioUsageItems();
-        mCar = Car.createCar(getContext(), mServiceConnection);
-        mListView = getView().findViewById(R.id.list);
-        mPagedListAdapter = new ListItemAdapter(getContext(), new ListProvider(mVolumeLineItems));
-        mListView.setAdapter(mPagedListAdapter);
-        mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mCar.connect();
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        cleanUpVolumeLineItems();
-        cleanupAudioManager();
-        mCar.disconnect();
-    }
-
-    private void cleanUpVolumeLineItems() {
-        for (ListItem item : mVolumeLineItems) {
-            ((VolumeLineItem) item).stop();
-        }
-        mVolumeLineItems.clear();
-    }
-
-    private void loadAudioUsageItems() {
-        try (XmlResourceParser parser = getResources().getXml(R.xml.car_volume_items)) {
-            AttributeSet attrs = Xml.asAttributeSet(parser);
-            int type;
-            // Traverse to the first start tag
-            while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
-                    && type != XmlResourceParser.START_TAG) {
-            }
-
-            if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
-                throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
-            }
-            int outerDepth = parser.getDepth();
-            int rank = 0;
-            while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
-                    && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == XmlResourceParser.END_TAG) {
-                    continue;
-                }
-                if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
-                    TypedArray item = getResources().obtainAttributes(
-                            attrs, R.styleable.carVolumeItems_item);
-                    int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
-                    if (usage >= 0) {
-                        mVolumeItems.put(usage, new VolumeItem(
-                                usage, rank,
-                                item.getResourceId(R.styleable.carVolumeItems_item_title, 0),
-                                item.getResourceId(R.styleable.carVolumeItems_item_icon, 0)));
-                        rank++;
-                    }
-                    item.recycle();
-                }
-            }
-        } catch (XmlPullParserException | IOException e) {
-            LOG.e("Error parsing volume groups configuration", e);
-        }
-    }
-
-    private VolumeItem getVolumeItemForUsages(int[] usages) {
-        int rank = Integer.MAX_VALUE;
-        VolumeItem result = null;
-        for (int usage : usages) {
-            VolumeItem volumeItem = mVolumeItems.get(usage);
-            if (volumeItem.rank < rank) {
-                rank = volumeItem.rank;
-                result = volumeItem;
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Wrapper class which contains information to render volume item on UI.
-     */
-    private static class VolumeItem {
-        private final @AudioAttributes.AttributeUsage int usage;
-        private final int rank;
-        private final @StringRes int title;
-        private final @DrawableRes int icon;
-
-        private VolumeItem(@AudioAttributes.AttributeUsage int usage, int rank,
-                @StringRes int title, @DrawableRes int icon) {
-            this.usage = usage;
-            this.rank = rank;
-            this.title = title;
-            this.icon = icon;
-        }
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.sound_settings_fragment;
     }
 }
diff --git a/src/com/android/car/settings/sound/VolumeItemParser.java b/src/com/android/car/settings/sound/VolumeItemParser.java
new file mode 100644
index 0000000..562d200
--- /dev/null
+++ b/src/com/android/car/settings/sound/VolumeItemParser.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 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.car.settings.sound;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.media.AudioAttributes;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.StringRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Parses the xml file which specifies which Audio usages should be considered by sound settings.
+ */
+public class VolumeItemParser {
+    private static final Logger LOG = new Logger(VolumeItemParser.class);
+
+    private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
+    private static final String XML_TAG_VOLUME_ITEM = "item";
+
+    /**
+     * Parses the volume items listed in the xml resource provided. This is returned as a sparse
+     * array which is keyed by the rank (the order in which the volume item appears in the xml
+     * resrouce).
+     */
+    public static SparseArray<VolumeItem> loadAudioUsageItems(Context context,
+            @XmlRes int volumeItemsXml) {
+        SparseArray<VolumeItem> volumeItems = new SparseArray<>();
+        try (XmlResourceParser parser = context.getResources().getXml(volumeItemsXml)) {
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            int type;
+            // Traverse to the first start tag.
+            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                    && type != XmlResourceParser.START_TAG) {
+                continue;
+            }
+
+            if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
+                throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
+            }
+            int outerDepth = parser.getDepth();
+            int rank = 0;
+            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                    && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlResourceParser.END_TAG) {
+                    continue;
+                }
+                if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
+                    TypedArray item = context.getResources().obtainAttributes(
+                            attrs, R.styleable.carVolumeItems_item);
+                    int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
+                    if (usage >= 0) {
+                        volumeItems.put(usage, new VolumeItemParser.VolumeItem(
+                                usage, rank,
+                                item.getResourceId(R.styleable.carVolumeItems_item_titleText, 0),
+                                item.getResourceId(R.styleable.carVolumeItems_item_icon, 0)));
+                        rank++;
+                    }
+                    item.recycle();
+                }
+            }
+        } catch (XmlPullParserException | IOException e) {
+            LOG.e("Error parsing volume groups configuration", e);
+        }
+        return volumeItems;
+    }
+
+    /**
+     * Wrapper class which contains information to render volume item on UI.
+     */
+    public static class VolumeItem {
+        @AudioAttributes.AttributeUsage
+        private final int mUsage;
+        private final int mRank;
+        @StringRes
+        private final int mTitle;
+        @DrawableRes
+        private final int mIcon;
+
+        /** Constructs the VolumeItem container with the given values. */
+        public VolumeItem(@AudioAttributes.AttributeUsage int usage, int rank,
+                @StringRes int title, @DrawableRes int icon) {
+            mUsage = usage;
+            mRank = rank;
+            mTitle = title;
+            mIcon = icon;
+        }
+
+        /**
+         * Usage is used to represent what purpose the sound is used for. The values should be
+         * defined within AudioAttributes.USAGE_*.
+         */
+        public int getUsage() {
+            return mUsage;
+        }
+
+        /**
+         * Rank represents the order in which the usage appears in
+         * {@link R.xml#car_volume_items}. This order is used to determine which title and icon
+         * should be used for each audio group. The lowest rank has the highest precedence.
+         */
+        public int getRank() {
+            return mRank;
+        }
+
+        /** Title which should be used for the seek bar preference. */
+        public int getTitle() {
+            return mTitle;
+        }
+
+        /** Icon which should be used for the seek bar preference. */
+        public int getIcon() {
+            return mIcon;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/sound/VolumeLineItem.java b/src/com/android/car/settings/sound/VolumeLineItem.java
deleted file mode 100644
index a628ad5..0000000
--- a/src/com/android/car/settings/sound/VolumeLineItem.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.settings.sound;
-
-import android.annotation.DrawableRes;
-import android.annotation.StringRes;
-import android.car.CarNotConnectedException;
-import android.car.media.CarAudioManager;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.widget.SeekBar;
-
-import androidx.car.widget.SeekbarListItem;
-
-import com.android.car.settings.common.Logger;
-
-/**
- * Contains logic about volume controller UI.
- */
-public class VolumeLineItem extends SeekbarListItem implements SeekBar.OnSeekBarChangeListener {
-    private static final Logger LOG = new Logger(VolumeLineItem.class);
-    private static final int AUDIO_FEEDBACK_DURATION_MS = 1000;
-
-    private final Handler mUiHandler;
-    private final Ringtone mRingtone;
-    private final int mVolumeGroupId;
-    private final CarAudioManager mCarAudioManager;
-
-    public VolumeLineItem(
-            Context context,
-            CarAudioManager carAudioManager,
-            int volumeGroupId,
-            @AudioAttributes.AttributeUsage int usage,
-            @DrawableRes int iconResId,
-            @StringRes int titleId) throws CarNotConnectedException {
-        super(context);
-        mCarAudioManager = carAudioManager;
-        mUiHandler = new Handler(Looper.getMainLooper());
-        mRingtone = RingtoneManager.getRingtone(context, getRingtoneUri(usage));
-        mRingtone.setAudioAttributes(new AudioAttributes.Builder().setUsage(usage).build());
-        mVolumeGroupId = volumeGroupId;
-        setMax(getMaxSeekbarValue());
-        updateProgress();
-        setOnSeekBarChangeListener(this);
-        setText(context.getString(titleId));
-        setPrimaryActionIcon(iconResId);
-    }
-
-    @Override
-    public void onStartTrackingTouch(SeekBar seekBar) {
-        // no-op
-    }
-
-    @Override
-    public void onStopTrackingTouch(SeekBar seekBar) {
-        // no-op
-    }
-
-    @Override
-    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-        if (!fromUser) {
-            // For instance, if this event is originated from AudioService,
-            // we can ignore it as it has already been handled and doesn't need to be
-            // sent back down again.
-            return;
-        }
-        try {
-            if (mCarAudioManager == null) {
-                LOG.w("Ignoring volume change event because the car isn't connected");
-                return;
-            }
-            // AudioManager.FLAG_PLAY_SOUND does not guarantee play sound, use our own
-            // playback here instead.
-            mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
-            playAudioFeedback();
-        } catch (CarNotConnectedException e) {
-            LOG.e("Car is not connected!", e);
-        }
-    }
-
-    /**
-     * Clean ups
-     */
-    public void stop() {
-        mUiHandler.removeCallbacksAndMessages(null);
-        mRingtone.stop();
-    }
-
-    public int getVolumeGroupId() {
-        return mVolumeGroupId;
-    }
-
-    /**
-     * Gets the latest progress.
-     */
-    public void updateProgress() {
-        setProgress(getSeekbarValue());
-    }
-
-    private void playAudioFeedback() {
-        mUiHandler.removeCallbacksAndMessages(null);
-        mRingtone.play();
-        mUiHandler.postDelayed(() -> {
-            if (mRingtone.isPlaying()) {
-                mRingtone.stop();
-            }
-        }, AUDIO_FEEDBACK_DURATION_MS);
-    }
-
-    // TODO: bundle car-specific audio sample assets in res/raw by usage
-    private Uri getRingtoneUri(@AudioAttributes.AttributeUsage int usage) {
-        switch (usage) {
-            case AudioAttributes.USAGE_NOTIFICATION:
-                return Settings.System.DEFAULT_NOTIFICATION_URI;
-            case AudioAttributes.USAGE_ALARM:
-                return Settings.System.DEFAULT_ALARM_ALERT_URI;
-            default:
-                return Settings.System.DEFAULT_RINGTONE_URI;
-        }
-    }
-
-    private int getSeekbarValue() {
-        try {
-            return mCarAudioManager.getGroupVolume(mVolumeGroupId);
-        } catch (CarNotConnectedException e) {
-            LOG.e("Car is not connected!", e);
-        }
-        return 0;
-    }
-
-    private int getMaxSeekbarValue() {
-        try {
-            return mCarAudioManager.getGroupMaxVolume(mVolumeGroupId);
-        } catch (CarNotConnectedException e) {
-            LOG.e("Car is not connected!", e);
-        }
-        return 0;
-    }
-}
diff --git a/src/com/android/car/settings/sound/VolumeSettingsPreferenceController.java b/src/com/android/car/settings/sound/VolumeSettingsPreferenceController.java
new file mode 100644
index 0000000..4e92c0d
--- /dev/null
+++ b/src/com/android/car/settings/sound/VolumeSettingsPreferenceController.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2018 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.car.settings.sound;
+
+import static com.android.car.settings.sound.VolumeItemParser.VolumeItem;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.media.CarAudioManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.SparseArray;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.apps.common.util.Themes;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.SeekBarPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Business logic which parses car volume items into groups, creates a seek bar preference for each
+ * group, and interfaces with the ringtone manager and audio manager.
+ *
+ * @see VolumeSettingsRingtoneManager
+ * @see android.car.media.CarAudioManager
+ */
+public class VolumeSettingsPreferenceController extends PreferenceController<PreferenceGroup> {
+    private static final Logger LOG = new Logger(VolumeSettingsPreferenceController.class);
+    private static final String VOLUME_GROUP_KEY = "volume_group_key";
+    private static final String VOLUME_USAGE_KEY = "volume_usage_key";
+
+    private final SparseArray<VolumeItem> mVolumeItems;
+    private final List<SeekBarPreference> mVolumePreferences = new ArrayList<>();
+    private final VolumeSettingsRingtoneManager mRingtoneManager;
+
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            try {
+                mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+                int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
+                cleanUpVolumePreferences();
+                // Populates volume slider items from volume groups to UI.
+                for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
+                    VolumeItem volumeItem = getVolumeItemForUsages(
+                            mCarAudioManager.getUsagesForVolumeGroupId(groupId));
+                    SeekBarPreference volumePreference = createVolumeSeekBarPreference(
+                            groupId, volumeItem.getUsage(), volumeItem.getIcon(),
+                            volumeItem.getTitle());
+                    mVolumePreferences.add(volumePreference);
+                }
+
+                refreshUi();
+            } catch (CarNotConnectedException e) {
+                LOG.e("Car is not connected!", e);
+            }
+        }
+
+        /** Cleanup audio related fields when car is disconnected. */
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            cleanupAudioManager();
+        }
+    };
+
+    private Car mCar;
+    private CarAudioManager mCarAudioManager;
+
+    public VolumeSettingsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCar = Car.createCar(getContext(), mServiceConnection);
+        mVolumeItems = VolumeItemParser.loadAudioUsageItems(context, carVolumeItemsXml());
+        mRingtoneManager = new VolumeSettingsRingtoneManager(getContext());
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /** Connect to car on create. */
+    @Override
+    protected void onCreateInternal() {
+        mCar.connect();
+    }
+
+    /** Disconnect from car on destroy. */
+    @Override
+    protected void onDestroyInternal() {
+        mCar.disconnect();
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        for (SeekBarPreference preference : mVolumePreferences) {
+            preferenceGroup.addPreference(preference);
+        }
+    }
+
+    /**
+     * The resource which lists the car volume resources associated with the various usage enums.
+     */
+    @XmlRes
+    @VisibleForTesting
+    int carVolumeItemsXml() {
+        return R.xml.car_volume_items;
+    }
+
+    private SeekBarPreference createVolumeSeekBarPreference(
+            int volumeGroupId, int usage, @DrawableRes int iconResId,
+            @StringRes int titleId) {
+        SeekBarPreference preference = new SeekBarPreference(getContext());
+        preference.setTitle(getContext().getString(titleId));
+        preference.setIcon(getContext().getDrawable(iconResId));
+        preference.getIcon().setTint(Themes.getAttrColor(getContext(), R.attr.iconColor));
+        try {
+            preference.setValue(mCarAudioManager.getGroupVolume(volumeGroupId));
+            preference.setMin(mCarAudioManager.getGroupMinVolume(volumeGroupId));
+            preference.setMax(mCarAudioManager.getGroupMaxVolume(volumeGroupId));
+        } catch (CarNotConnectedException e) {
+            LOG.e("Car is not connected!", e);
+        }
+        preference.setContinuousUpdate(true);
+        preference.setShowSeekBarValue(false);
+        Bundle bundle = preference.getExtras();
+        bundle.putInt(VOLUME_GROUP_KEY, volumeGroupId);
+        bundle.putInt(VOLUME_USAGE_KEY, usage);
+        preference.setOnPreferenceChangeListener((pref, newValue) -> {
+            int prefGroup = pref.getExtras().getInt(VOLUME_GROUP_KEY);
+            int prefUsage = pref.getExtras().getInt(VOLUME_USAGE_KEY);
+            int newVolume = (Integer) newValue;
+            setGroupVolume(prefGroup, newVolume);
+            mRingtoneManager.playAudioFeedback(prefGroup, prefUsage);
+            return true;
+        });
+        return preference;
+    }
+
+    private void setGroupVolume(int volumeGroupId, int newVolume) {
+        try {
+            mCarAudioManager.setGroupVolume(volumeGroupId, newVolume, /* flags= */ 0);
+        } catch (CarNotConnectedException e) {
+            LOG.w("Ignoring volume change event because the car isn't connected", e);
+        }
+    }
+
+    private void cleanupAudioManager() {
+        cleanUpVolumePreferences();
+        mCarAudioManager = null;
+    }
+
+    private void cleanUpVolumePreferences() {
+        mRingtoneManager.stopCurrentRingtone();
+        mVolumePreferences.clear();
+    }
+
+    private VolumeItem getVolumeItemForUsages(int[] usages) {
+        int rank = Integer.MAX_VALUE;
+        VolumeItem result = null;
+        for (int usage : usages) {
+            VolumeItem volumeItem = mVolumeItems.get(usage);
+            if (volumeItem.getRank() < rank) {
+                rank = volumeItem.getRank();
+                result = volumeItem;
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/car/settings/sound/VolumeSettingsRingtoneManager.java b/src/com/android/car/settings/sound/VolumeSettingsRingtoneManager.java
new file mode 100644
index 0000000..51f8bd8
--- /dev/null
+++ b/src/com/android/car/settings/sound/VolumeSettingsRingtoneManager.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 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.car.settings.sound;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Manges the audio played by the {@link VolumeSettingsPreferenceController}. */
+public class VolumeSettingsRingtoneManager {
+
+    private static final int AUDIO_FEEDBACK_DURATION_MS = 1000;
+
+    private final Context mContext;
+    private final Handler mUiHandler;
+    private final Map<Integer, Ringtone> mGroupToRingtoneMap = new HashMap<>();
+
+    @Nullable
+    private Ringtone mCurrentRingtone;
+
+    public VolumeSettingsRingtoneManager(Context context) {
+        mContext = context;
+        mUiHandler = new Handler(Looper.getMainLooper());
+    }
+
+    /**
+     * Play the audio defined by the current group and usage. Stop the current ringtone if it is a
+     * different ringtone than what is currently playing.
+     */
+    public void playAudioFeedback(int group, int usage) {
+        Ringtone nextRingtone = lazyLoadRingtone(group, usage);
+        if (mCurrentRingtone != null && mCurrentRingtone != nextRingtone
+                && mCurrentRingtone.isPlaying()) {
+            mCurrentRingtone.stop();
+        }
+
+        mUiHandler.removeCallbacksAndMessages(null);
+        mCurrentRingtone = nextRingtone;
+        mCurrentRingtone.play();
+        mUiHandler.postDelayed(() -> {
+            if (mCurrentRingtone.isPlaying()) {
+                mCurrentRingtone.stop();
+                mCurrentRingtone = null;
+            }
+        }, AUDIO_FEEDBACK_DURATION_MS);
+    }
+
+    /** Stop playing the current ringtone. */
+    public void stopCurrentRingtone() {
+        if (mCurrentRingtone != null) {
+            mCurrentRingtone.stop();
+        }
+    }
+
+    /** If we have already seen this ringtone, use it. Otherwise load when requested. */
+    private Ringtone lazyLoadRingtone(int group, int usage) {
+        if (!mGroupToRingtoneMap.containsKey(group)) {
+            Ringtone ringtone = RingtoneManager.getRingtone(mContext, getRingtoneUri(usage));
+            ringtone.setAudioAttributes(new AudioAttributes.Builder().setUsage(usage).build());
+            mGroupToRingtoneMap.put(group, ringtone);
+        }
+        return mGroupToRingtoneMap.get(group);
+    }
+
+    // TODO: bundle car-specific audio sample assets in res/raw by usage
+    private Uri getRingtoneUri(@AudioAttributes.AttributeUsage int usage) {
+        switch (usage) {
+            case AudioAttributes.USAGE_NOTIFICATION:
+                return Settings.System.DEFAULT_NOTIFICATION_URI;
+            case AudioAttributes.USAGE_ALARM:
+                return Settings.System.DEFAULT_ALARM_ALERT_URI;
+            default:
+                return Settings.System.DEFAULT_RINGTONE_URI;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragment.java b/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragment.java
new file mode 100644
index 0000000..08f65dc
--- /dev/null
+++ b/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragment.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.app.ActivityManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+import androidx.loader.app.LoaderManager;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.StorageStatsSource;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Fragment to display the applications storage information. Also provide buttons to clear the
+ * applications cache data and user data.
+ */
+public class AppStorageSettingsDetailsFragment extends SettingsFragment implements
+        AppsStorageStatsManager.Callback {
+    private static final Logger LOG = new Logger(AppStorageSettingsDetailsFragment.class);
+
+    @VisibleForTesting
+    static final String CONFIRM_CLEAR_STORAGE_DIALOG_TAG =
+            "com.android.car.settings.storage.ConfirmClearStorageDialog";
+
+    @VisibleForTesting
+    static final String CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG =
+            "com.android.car.settings.storage.ConfirmCannotClearStorageDialog";
+
+    public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
+    // Result code identifiers
+    public static final int REQUEST_MANAGE_SPACE = 2;
+
+    // Internal constants used in Handler
+    private static final int OP_SUCCESSFUL = 1;
+    private static final int OP_FAILED = 2;
+
+    // Constant used in handler to determine when the user data is cleared.
+    private static final int MSG_CLEAR_USER_DATA = 1;
+    // Constant used in handler to determine when the cache is cleared.
+    private static final int MSG_CLEAR_CACHE = 2;
+
+    // Keys to save the instance values.
+    private static final String KEY_CACHE_CLEARED = "cache_cleared";
+    private static final String KEY_DATA_CLEARED = "data_cleared";
+
+    // Package information
+    protected PackageManager mPackageManager;
+    private String mPackageName;
+
+    // Application state info
+    private ApplicationsState.AppEntry mAppEntry;
+    private ApplicationsState mAppState;
+    private ApplicationInfo mInfo;
+    private AppsStorageStatsManager mAppsStorageStatsManager;
+
+    // User info
+    private int mUserId;
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    //  An observer callback to get notified when the cache file deletion is complete.
+    private ClearCacheObserver mClearCacheObserver;
+    //  An observer callback to get notified when the user data deletion is complete.
+    private ClearUserDataObserver mClearDataObserver;
+
+    // The restriction enforced by admin.
+    private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin;
+    private boolean mAppsControlDisallowedBySystem;
+
+    // Clear user data and cache buttons and state.
+    private Button mClearStorageButton;
+    private Button mClearCacheButton;
+    private boolean mCanClearData = true;
+    private boolean mCacheCleared;
+    private boolean mDataCleared;
+
+    private final ConfirmationDialogFragment.ConfirmListener mConfirmClearStorageDialog =
+            arguments -> initiateClearUserData();
+
+
+    private final ConfirmationDialogFragment.ConfirmListener mConfirmCannotClearStorageDialog =
+            arguments -> mClearStorageButton.setEnabled(false);
+
+    /** Creates an instance of this fragment, passing packageName as an argument. */
+    public static AppStorageSettingsDetailsFragment getInstance(String packageName) {
+        AppStorageSettingsDetailsFragment applicationDetailFragment =
+                new AppStorageSettingsDetailsFragment();
+        Bundle bundle = new Bundle();
+        bundle.putString(EXTRA_PACKAGE_NAME, packageName);
+        applicationDetailFragment.setArguments(bundle);
+        return applicationDetailFragment;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.app_storage_settings_details_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        mUserId = mCarUserManagerHelper.getCurrentProcessUserId();
+        mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME);
+        mAppState = ApplicationsState.getInstance(requireActivity().getApplication());
+        mAppEntry = mAppState.getEntry(mPackageName, mUserId);
+        StorageStatsSource storageStatsSource = new StorageStatsSource(context);
+        StorageStatsSource.AppStorageStats stats = null;
+        mPackageManager = context.getPackageManager();
+        try {
+            stats = storageStatsSource.getStatsForPackage(/* volumeUuid= */ null, mPackageName,
+                    UserHandle.of(mUserId));
+        } catch (Exception e) {
+            // This may happen if the package was removed during our calculation.
+            LOG.w("App unexpectedly not found", e);
+        }
+        mAppsStorageStatsManager = new AppsStorageStatsManager(context);
+        mAppsStorageStatsManager.registerListener(this);
+        use(StorageApplicationPreferenceController.class,
+                R.string.pk_storage_application_details)
+                .setAppEntry(mAppEntry)
+                .setAppState(mAppState);
+
+        List<StorageSizeBasePreferenceController> preferenceControllers = Arrays.asList(
+                use(StorageApplicationSizePreferenceController.class,
+                        R.string.pk_storage_application_size),
+                use(StorageApplicationTotalSizePreferenceController.class,
+                        R.string.pk_storage_application_total_size),
+                use(StorageApplicationUserDataPreferenceController.class,
+                        R.string.pk_storage_application_data_size),
+                use(StorageApplicationCacheSizePreferenceController.class,
+                        R.string.pk_storage_application_cache_size)
+        );
+
+        for (StorageSizeBasePreferenceController pc : preferenceControllers) {
+            pc.setAppsStorageStatsManager(mAppsStorageStatsManager);
+            pc.setAppStorageStats(stats);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_CACHE_CLEARED, mCacheCleared);
+        outState.putBoolean(KEY_DATA_CLEARED, mDataCleared);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mCacheCleared = savedInstanceState.getBoolean(KEY_CACHE_CLEARED, false);
+            mDataCleared = savedInstanceState.getBoolean(KEY_DATA_CLEARED, false);
+            mCacheCleared = mCacheCleared || mDataCleared;
+        }
+        ConfirmationDialogFragment.resetListeners(
+                (ConfirmationDialogFragment) findDialogByTag(CONFIRM_CLEAR_STORAGE_DIALOG_TAG),
+                mConfirmClearStorageDialog, /* rejectListener= */ null);
+        ConfirmationDialogFragment.resetListeners(
+                (ConfirmationDialogFragment) findDialogByTag(
+                        CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG),
+                mConfirmCannotClearStorageDialog, /* rejectListener= */ null);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        mClearStorageButton = requireActivity().findViewById(R.id.action_button1);
+        mClearStorageButton.setVisibility(View.VISIBLE);
+        mClearStorageButton.setEnabled(false);
+        mClearStorageButton.setText(R.string.storage_clear_user_data_text);
+
+        mClearCacheButton = requireActivity().findViewById(R.id.action_button2);
+        mClearCacheButton.setVisibility(View.VISIBLE);
+        mClearCacheButton.setEnabled(false);
+        mClearCacheButton.setText(R.string.storage_clear_cache_btn_text);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
+                UserManager.DISALLOW_APPS_CONTROL, mUserId);
+        mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(getActivity(),
+                UserManager.DISALLOW_APPS_CONTROL, mUserId);
+        updateSize();
+    }
+
+    @Override
+    public void onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared,
+            boolean dataCleared) {
+        if (data == null) {
+            mClearStorageButton.setEnabled(false);
+            mClearCacheButton.setEnabled(false);
+        } else {
+            long cacheSize = data.getCacheBytes();
+            long dataSize = data.getDataBytes() - cacheSize;
+
+            if (dataSize <= 0 || !mCanClearData || mDataCleared) {
+                mClearStorageButton.setEnabled(false);
+            } else {
+                mClearStorageButton.setEnabled(true);
+                mClearStorageButton.setOnClickListener(v -> handleClearDataClick());
+            }
+            if (cacheSize <= 0 || mCacheCleared) {
+                mClearCacheButton.setEnabled(false);
+            } else {
+                mClearCacheButton.setEnabled(true);
+                mClearCacheButton.setOnClickListener(v -> handleClearCacheClick());
+            }
+        }
+        if (mAppsControlDisallowedBySystem) {
+            mClearStorageButton.setEnabled(false);
+            mClearCacheButton.setEnabled(false);
+        }
+    }
+
+    private void handleClearCacheClick() {
+        if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
+            RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+                    getActivity(), mAppsControlDisallowedAdmin);
+            return;
+        }
+        // Lazy initialization of observer.
+        if (mClearCacheObserver == null) {
+            mClearCacheObserver = new ClearCacheObserver();
+        }
+        mPackageManager.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver);
+    }
+
+    private void handleClearDataClick() {
+        if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
+            RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+                    getActivity(), mAppsControlDisallowedAdmin);
+        } else if (mAppEntry.info.manageSpaceActivityName != null) {
+            Intent intent = new Intent(Intent.ACTION_DEFAULT);
+            intent.setClassName(mAppEntry.info.packageName,
+                    mAppEntry.info.manageSpaceActivityName);
+            startActivityForResult(intent, REQUEST_MANAGE_SPACE);
+        } else {
+            showClearDataDialog();
+        }
+    }
+
+    /*
+     * Private method to initiate clearing user data when the user clicks the clear data
+     * button for a system package
+     */
+    private void initiateClearUserData() {
+        mClearStorageButton.setEnabled(false);
+        // Invoke uninstall or clear user data based on sysPackage
+        String packageName = mAppEntry.info.packageName;
+        LOG.i("Clearing user data for package : " + packageName);
+        if (mClearDataObserver == null) {
+            mClearDataObserver = new ClearUserDataObserver();
+        }
+        ActivityManager am = (ActivityManager)
+                getActivity().getSystemService(Context.ACTIVITY_SERVICE);
+        boolean res = am.clearApplicationUserData(packageName, mClearDataObserver);
+        if (!res) {
+            // Clearing data failed for some obscure reason. Just log error for now
+            LOG.i("Couldn't clear application user data for package:" + packageName);
+            showCannotClearDataDialog();
+        }
+    }
+
+    /*
+     * Private method to handle clear message notification from observer when
+     * the async operation from PackageManager is complete
+     */
+    private void processClearMsg(Message msg) {
+        int result = msg.arg1;
+        String packageName = mAppEntry.info.packageName;
+        if (result == OP_SUCCESSFUL) {
+            LOG.i("Cleared user data for package : " + packageName);
+            updateSize();
+        } else {
+            mClearStorageButton.setEnabled(true);
+        }
+    }
+
+    private void updateSize() {
+        PackageManager packageManager = getActivity().getPackageManager();
+        try {
+            mInfo = packageManager.getApplicationInfo(mPackageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            LOG.e("Could not find package", e);
+        }
+        if (mInfo == null) {
+            return;
+        }
+        LoaderManager loaderManager = LoaderManager.getInstance(this);
+        mAppsStorageStatsManager.startLoading(loaderManager, mInfo, mUserId, mCacheCleared,
+                mDataCleared);
+    }
+
+    private void showClearDataDialog() {
+        ConfirmationDialogFragment confirmClearStorageDialog =
+                new ConfirmationDialogFragment.Builder(getContext())
+                        .setTitle(R.string.storage_clear_user_data_text)
+                        .setMessage(getString(R.string.storage_clear_data_dlg_text))
+                        .setPositiveButton(R.string.okay, mConfirmClearStorageDialog)
+                        .setNegativeButton(android.R.string.cancel, /* rejectListener= */ null)
+                        .build();
+        showDialog(confirmClearStorageDialog, CONFIRM_CLEAR_STORAGE_DIALOG_TAG);
+    }
+
+    private void showCannotClearDataDialog() {
+        ConfirmationDialogFragment dialogFragment =
+                new ConfirmationDialogFragment.Builder(getContext())
+                        .setTitle(R.string.storage_clear_data_dlg_title)
+                        .setMessage(getString(R.string.storage_clear_failed_dlg_text))
+                        .setPositiveButton(R.string.okay, mConfirmCannotClearStorageDialog)
+                        .build();
+        showDialog(dialogFragment, CONFIRM_CANNOT_CLEAR_STORAGE_DIALOG_TAG);
+    }
+
+    private final Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            if (getView() == null) {
+                return;
+            }
+            switch (msg.what) {
+                case MSG_CLEAR_USER_DATA:
+                    mDataCleared = true;
+                    mCacheCleared = true;
+                    processClearMsg(msg);
+                    break;
+                case MSG_CLEAR_CACHE:
+                    mCacheCleared = true;
+                    // Refresh size info
+                    updateSize();
+                    break;
+            }
+        }
+    };
+
+    class ClearCacheObserver extends IPackageDataObserver.Stub {
+        public void onRemoveCompleted(final String packageName, final boolean succeeded) {
+            Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE);
+            msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED;
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    class ClearUserDataObserver extends IPackageDataObserver.Stub {
+        public void onRemoveCompleted(final String packageName, final boolean succeeded) {
+            Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA);
+            msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED;
+            mHandler.sendMessage(msg);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/storage/AppsStorageStatsManager.java b/src/com/android/car/settings/storage/AppsStorageStatsManager.java
new file mode 100644
index 0000000..8b78e4e
--- /dev/null
+++ b/src/com/android/car/settings/storage/AppsStorageStatsManager.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+
+import com.android.settingslib.applications.StorageStatsSource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to manage the callbacks needed to calculate storage stats for an application.
+ */
+public class AppsStorageStatsManager {
+
+    /**
+     * Callback that is called once the AppsStorageStats is loaded.
+     */
+    public interface Callback {
+        /**
+         * Called when the data is successfully loaded from {@link AppsStorageStatsResult}. The
+         * result can be {@link null} if the package is removed during loading. Also notifies if
+         * this callback was initiated when cache or data is cleared or not.
+         */
+        void onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared,
+                boolean dataCleared);
+    }
+
+    private final Context mContext;
+    private ApplicationInfo mInfo;
+    private int mUserId;
+    private boolean mCacheCleared;
+    private boolean mDataCleared;
+    private List<Callback> mAppsStorageStatsListeners = new ArrayList<>();
+
+    AppsStorageStatsManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Registers a listener that will be notified once the data is loaded.
+     */
+    public void registerListener(Callback appsStorageStatsListener) {
+        if (!mAppsStorageStatsListeners.contains(appsStorageStatsListener)) {
+            mAppsStorageStatsListeners.add(appsStorageStatsListener);
+        }
+    }
+
+    /**
+     * Unregisters the listener.
+     */
+    public void unregisterListener(Callback appsStorageStatsListener) {
+        mAppsStorageStatsListeners.remove(appsStorageStatsListener);
+    }
+
+    /**
+     * Start calculating the storage stats.
+     */
+    public void startLoading(LoaderManager loaderManager, ApplicationInfo info, int userId,
+            boolean cacheCleared, boolean dataCleared) {
+        mInfo = info;
+        mUserId = userId;
+        mCacheCleared = cacheCleared;
+        mDataCleared = dataCleared;
+        loaderManager.restartLoader(/* id= */ 1, Bundle.EMPTY, new AppsStorageStatsResult());
+    }
+
+    private void onAppsStorageStatsLoaded(StorageStatsSource.AppStorageStats data) {
+        for (Callback listener : mAppsStorageStatsListeners) {
+            listener.onDataLoaded(data, mCacheCleared, mDataCleared);
+        }
+    }
+
+    /**
+     * Callback to calculate applications storage stats.
+     */
+    private class AppsStorageStatsResult implements
+            LoaderManager.LoaderCallbacks<StorageStatsSource.AppStorageStats> {
+
+        @NonNull
+        @Override
+        public Loader<StorageStatsSource.AppStorageStats> onCreateLoader(int id,
+                @Nullable Bundle args) {
+            return new FetchPackageStorageAsyncLoader(
+                    mContext, new StorageStatsSource(mContext), mInfo, UserHandle.of(mUserId));
+        }
+
+        @Override
+        public void onLoadFinished(
+                @NonNull Loader<StorageStatsSource.AppStorageStats> loader,
+                StorageStatsSource.AppStorageStats data) {
+            onAppsStorageStatsLoaded(data);
+        }
+
+        @Override
+        public void onLoaderReset(
+                @NonNull Loader<StorageStatsSource.AppStorageStats> loader) {
+        }
+    }
+}
diff --git a/src/com/android/car/settings/storage/FetchPackageStorageAsyncLoader.java b/src/com/android/car/settings/storage/FetchPackageStorageAsyncLoader.java
new file mode 100644
index 0000000..1c942b3
--- /dev/null
+++ b/src/com/android/car/settings/storage/FetchPackageStorageAsyncLoader.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.car.settingslib.loader.AsyncLoader;
+import com.android.internal.util.Preconditions;
+import com.android.settingslib.applications.StorageStatsSource;
+import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
+
+import java.io.IOException;
+
+/**
+ * Fetches the storage stats using the {@link StorageStatsSource} for a given package and user
+ * tuple.
+ *
+ * <p>Class is taken from {@link com.android.settings.applications.FetchPackageStorageAsyncLoader}
+ */
+public class FetchPackageStorageAsyncLoader extends AsyncLoader<AppStorageStats> {
+    private static final String TAG = "FetchPackageStorage";
+    private final StorageStatsSource mSource;
+    private final ApplicationInfo mInfo;
+    private final UserHandle mUser;
+
+    public FetchPackageStorageAsyncLoader(Context context, @NonNull StorageStatsSource source,
+            @NonNull ApplicationInfo info, @NonNull UserHandle user) {
+        super(context);
+        mSource = Preconditions.checkNotNull(source);
+        mInfo = info;
+        mUser = user;
+    }
+
+    @Override
+    public AppStorageStats loadInBackground() {
+        AppStorageStats result = null;
+        try {
+            result = mSource.getStatsForPackage(mInfo.volumeUuid, mInfo.packageName, mUser);
+        } catch (NameNotFoundException | IOException e) {
+            Log.w(TAG, "Package may have been removed during query, failing gracefully", e);
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/car/settings/storage/FileSizeFormatter.java b/src/com/android/car/settings/storage/FileSizeFormatter.java
new file mode 100644
index 0000000..789c4a3
--- /dev/null
+++ b/src/com/android/car/settings/storage/FileSizeFormatter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.BidiFormatter;
+import android.text.format.Formatter;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Utility class to aid in formatting file sizes always with the same unit. This is modified from
+ * android.text.format.Formatter to fit this purpose.
+ *
+ * <p>Class is taken from {@link com.android.settings.utils.FileSizeFormatter}
+ */
+public final class FileSizeFormatter {
+    public static final long KILOBYTE_IN_BYTES = 1000;
+    public static final long MEGABYTE_IN_BYTES = KILOBYTE_IN_BYTES * 1000;
+    public static final long GIGABYTE_IN_BYTES = MEGABYTE_IN_BYTES * 1000;
+
+    private FileSizeFormatter() {
+    }
+
+    /**
+     * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc.
+     *
+     * <p>The prefixes are used in their standard meanings in the SI system, so kB = 1000
+     * bytes, MB = 1,000,000 bytes, etc.
+     *
+     * <p>If the context has a right-to-left locale, the returned string is wrapped in bidi
+     * formatting characters to make sure it's displayed correctly if inserted inside a
+     * right-to-left string. (This is useful in cases where the unit strings, like "MB", are
+     * left-to-right, but the locale is right-to-left.)
+     *
+     * @param context Context to use to load the localized units
+     * @param sizeBytes size value to be formatted, in bytes
+     * @param suffix String id for the unit suffix.
+     * @param mult Amount of bytes in the unit. * @return formatted string with the number
+     */
+    public static String formatFileSize(
+            @NonNull Context context, long sizeBytes, int suffix, long mult) {
+        Formatter.BytesResult res =
+                formatBytes(context.getResources(), sizeBytes, suffix, mult);
+        return BidiFormatter.getInstance()
+                .unicodeWrap(context.getString(getFileSizeSuffix(context), res.value, res.units));
+    }
+
+    private static int getFileSizeSuffix(Context context) {
+        Resources res = context.getResources();
+        return res.getIdentifier("fileSizeSuffix", "string", "android");
+    }
+
+    /**
+     * A simplified version of the SettingsLib file size formatter. The primary difference is that
+     * this version always assumes it is doing a "short file size" and allows for a suffix to be
+     * provided.
+     *
+     * @param res Resources to fetch strings with.
+     * @param sizeBytes File size in bytes to format.
+     * @param suffix String id for the unit suffix.
+     * @param mult Amount of bytes in the unit.
+     */
+    private static Formatter.BytesResult formatBytes(
+            Resources res, long sizeBytes, int suffix, long mult) {
+        boolean isNegative = (sizeBytes < 0);
+        float result = isNegative ? -sizeBytes : sizeBytes;
+        result = result / mult;
+        // Note we calculate the rounded long by ourselves, but still let String.format()
+        // compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to
+        // floating point errors.
+        int roundFactor;
+        String roundFormat;
+        if (mult == 1) {
+            roundFactor = 1;
+            roundFormat = "%.0f";
+        } else if (result < 1) {
+            roundFactor = 100;
+            roundFormat = "%.2f";
+        } else if (result < 10) {
+            roundFactor = 10;
+            roundFormat = "%.1f";
+        } else { // 10 <= result < 100
+            roundFactor = 1;
+            roundFormat = "%.0f";
+        }
+
+        if (isNegative) {
+            result = -result;
+        }
+        String roundedString = String.format(roundFormat, result);
+
+        // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so
+        // it's okay (for now)...
+        long roundedBytes = (((long) Math.round(result * roundFactor)) * mult / roundFactor);
+
+        String units = res.getString(suffix);
+
+        return new Formatter.BytesResult(roundedString, units, roundedBytes);
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageAppDetailPreference.java b/src/com/android/car/settings/storage/StorageAppDetailPreference.java
new file mode 100644
index 0000000..8621e11
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageAppDetailPreference.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.R;
+
+/**
+ * A Preference to be used on the storage size application details page where the summary is
+ * displayed towards the right.
+ */
+public class StorageAppDetailPreference extends Preference {
+    private String mDetailText;
+
+    public StorageAppDetailPreference(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+        setWidgetLayoutResource(R.layout.summary_preference_widget);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+        TextView textView = ((TextView) view.findViewById(R.id.widget_summary));
+        textView.setText(mDetailText);
+    }
+
+    @VisibleForTesting
+    StorageAppDetailPreference(Context context) {
+        super(context);
+    }
+
+    /**
+     * Sets the detail text.
+     */
+    public void setDetailText(String text) {
+        if (TextUtils.equals(mDetailText, text)) {
+            return;
+        }
+        mDetailText = text;
+        notifyChanged();
+    }
+
+    /**
+     * Gets the detail text.
+     */
+    public String getDetailText() {
+        return mDetailText;
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageApplicationCacheSizePreferenceController.java b/src/com/android/car/settings/storage/StorageApplicationCacheSizePreferenceController.java
new file mode 100644
index 0000000..dd7d5aa
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageApplicationCacheSizePreferenceController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Class to calculate the cache size for a particular application.
+ */
+public class StorageApplicationCacheSizePreferenceController extends
+        StorageSizeBasePreferenceController {
+
+    public StorageApplicationCacheSizePreferenceController(Context context,
+            String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected long getSize() {
+        return (isCachedCleared() || isDataCleared()) ? 0 : getAppStorageStats().getCacheBytes();
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageApplicationListPreferenceController.java b/src/com/android/car/settings/storage/StorageApplicationListPreferenceController.java
new file mode 100644
index 0000000..53ed01c
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageApplicationListPreferenceController.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.applications.ApplicationListItemManager;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.ArrayList;
+
+/**
+ * Controller that adds all the applications provided to it into the UI as preference. Each
+ * application is a new preference.
+ */
+public class StorageApplicationListPreferenceController extends
+        PreferenceController<PreferenceGroup> implements
+        ApplicationListItemManager.AppListItemListener {
+
+    public StorageApplicationListPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    public void onDataLoaded(ArrayList<ApplicationsState.AppEntry> apps) {
+        for (ApplicationsState.AppEntry appEntry : apps) {
+            addOrUpdatePreference(appEntry);
+        }
+    }
+
+    private void addOrUpdatePreference(ApplicationsState.AppEntry appEntry) {
+        Preference preference = getPreference().findPreference(appEntry.info.packageName);
+        if (preference != null) {
+            preference.setSummary(appEntry.sizeStr);
+            return;
+        }
+        getPreference().addPreference(
+                createPreference(appEntry.label, appEntry.sizeStr, appEntry.icon,
+                        appEntry.info.packageName));
+    }
+
+    protected Preference createPreference(String title, String summary, Drawable icon,
+            String key) {
+        Preference preference = new Preference(getContext());
+        preference.setTitle(title);
+        preference.setSummary(summary);
+        preference.setIcon(icon);
+        preference.setKey(key);
+        preference.setOnPreferenceClickListener(p -> {
+            getFragmentController().launchFragment(
+                    AppStorageSettingsDetailsFragment.getInstance(key));
+            return true;
+        });
+        return preference;
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageApplicationPreferenceController.java b/src/com/android/car/settings/storage/StorageApplicationPreferenceController.java
new file mode 100644
index 0000000..ed55031
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageApplicationPreferenceController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.applications.ApplicationPreferenceController;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Preference showing the application icon, title and version on the storage size application
+ * details page.
+ */
+public class StorageApplicationPreferenceController extends ApplicationPreferenceController {
+
+    public StorageApplicationPreferenceController(Context context,
+            String preferenceKey, FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setTitle(getAppName());
+        preference.setIcon(getAppIcon());
+        preference.setSummary(getAppVersion());
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageApplicationSizePreferenceController.java b/src/com/android/car/settings/storage/StorageApplicationSizePreferenceController.java
new file mode 100644
index 0000000..27df177
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageApplicationSizePreferenceController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Class to calculate the app size for a particular application.
+ */
+public class StorageApplicationSizePreferenceController extends
+        StorageSizeBasePreferenceController {
+
+    public StorageApplicationSizePreferenceController(Context context,
+            String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected long getSize() {
+        return getAppStorageStats().getCodeBytes();
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageApplicationTotalSizePreferenceController.java b/src/com/android/car/settings/storage/StorageApplicationTotalSizePreferenceController.java
new file mode 100644
index 0000000..4f42c6b
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageApplicationTotalSizePreferenceController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Class to calculate the total size for a particular application.
+ */
+public class StorageApplicationTotalSizePreferenceController extends
+        StorageSizeBasePreferenceController {
+
+    public StorageApplicationTotalSizePreferenceController(Context context,
+            String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected long getSize() {
+        long appSize = getAppStorageStats().getCodeBytes();
+        long userData = isDataCleared() ? 0
+                : getAppStorageStats().getDataBytes() - getAppStorageStats().getCacheBytes();
+        long cacheData =
+                (isCachedCleared() || isDataCleared()) ? 0 : getAppStorageStats().getCacheBytes();
+        return appSize + userData + cacheData;
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageApplicationUserDataPreferenceController.java b/src/com/android/car/settings/storage/StorageApplicationUserDataPreferenceController.java
new file mode 100644
index 0000000..33d470f
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageApplicationUserDataPreferenceController.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Class to calculate the user data size for a particular application.
+ */
+public class StorageApplicationUserDataPreferenceController extends
+        StorageSizeBasePreferenceController {
+
+    public StorageApplicationUserDataPreferenceController(Context context,
+            String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected long getSize() {
+        return isDataCleared() ? 0
+                : getAppStorageStats().getDataBytes() - getAppStorageStats().getCacheBytes();
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageAsyncLoader.java b/src/com/android/car/settings/storage/StorageAsyncLoader.java
new file mode 100644
index 0000000..204695f
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageAsyncLoader.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static android.content.pm.ApplicationInfo.CATEGORY_AUDIO;
+import static android.content.pm.ApplicationInfo.CATEGORY_GAME;
+import static android.content.pm.ApplicationInfo.CATEGORY_IMAGE;
+import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.car.settings.common.Logger;
+import com.android.car.settingslib.loader.AsyncLoader;
+import com.android.settingslib.applications.StorageStatsSource;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * {@link StorageAsyncLoader} is a Loader which loads categorized app information and external stats
+ * for all users.
+ *
+ * <p>Class is taken from {@link com.android.settings.deviceinfo.storage.StorageAsyncLoader}
+ */
+public class StorageAsyncLoader
+        extends AsyncLoader<SparseArray<StorageAsyncLoader.AppsStorageResult>> {
+    private static final Logger LOG = new Logger(StorageAsyncLoader.class);
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final StorageStatsSource mStatsManager;
+    private final PackageManager mPackageManager;
+
+    public StorageAsyncLoader(Context context, CarUserManagerHelper carUserManagerHelper,
+            StorageStatsSource source) {
+        super(context);
+        mCarUserManagerHelper = carUserManagerHelper;
+        mStatsManager = source;
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Override
+    public SparseArray<AppsStorageResult> loadInBackground() {
+        ArraySet<String> seenPackages = new ArraySet<>();
+        SparseArray<AppsStorageResult> result = new SparseArray<>();
+        List<UserInfo> infos = mCarUserManagerHelper.getAllUsers();
+        for (int i = 0, userCount = infos.size(); i < userCount; i++) {
+            UserInfo info = infos.get(i);
+            result.put(info.id, getStorageResultForUser(info.id, seenPackages));
+        }
+        return result;
+    }
+
+    private AppsStorageResult getStorageResultForUser(int userId, ArraySet<String> seenPackages) {
+        LOG.d("Loading apps");
+        List<ApplicationInfo> applicationInfos =
+                mPackageManager.getInstalledApplicationsAsUser(/* getAllInstalledApplications= */ 0,
+                        userId);
+        UserHandle myUser = UserHandle.of(userId);
+        long gameAppSize = 0;
+        long musicAppsSize = 0;
+        long videoAppsSize = 0;
+        long photosAppsSize = 0;
+        long otherAppsSize = 0;
+        for (int i = 0, size = applicationInfos.size(); i < size; i++) {
+            ApplicationInfo app = applicationInfos.get(i);
+            StorageStatsSource.AppStorageStats stats;
+            try {
+                stats = mStatsManager.getStatsForPackage(/* volumeUuid= */ null, app.packageName,
+                        myUser);
+            } catch (NameNotFoundException | IOException e) {
+                // This may happen if the package was removed during our calculation.
+                LOG.w("App unexpectedly not found", e);
+                continue;
+            }
+
+            long dataSize = stats.getDataBytes();
+            long cacheQuota = mStatsManager.getCacheQuotaBytes(/* volumeUuid= */null, app.uid);
+            long cacheBytes = stats.getCacheBytes();
+            long blamedSize = dataSize;
+            // Technically, we could show overages as freeable on the storage settings screen.
+            // If the app is using more cache than its quota, we would accidentally subtract the
+            // overage from the system size (because it shows up as unused) during our attribution.
+            // Thus, we cap the attribution at the quota size.
+            if (cacheQuota < cacheBytes) {
+                blamedSize = blamedSize - cacheBytes + cacheQuota;
+            }
+
+            // This isn't quite right because it slams the first user by user id with the whole code
+            // size, but this ensures that we count all apps seen once.
+            if (!seenPackages.contains(app.packageName)) {
+                blamedSize += stats.getCodeBytes();
+                seenPackages.add(app.packageName);
+            }
+
+            switch (app.category) {
+                case CATEGORY_GAME:
+                    gameAppSize += blamedSize;
+                    break;
+                case CATEGORY_AUDIO:
+                    musicAppsSize += blamedSize;
+                    break;
+                case CATEGORY_VIDEO:
+                    videoAppsSize += blamedSize;
+                    break;
+                case CATEGORY_IMAGE:
+                    photosAppsSize += blamedSize;
+                    break;
+                default:
+                    // The deprecated game flag does not set the category.
+                    if ((app.flags & ApplicationInfo.FLAG_IS_GAME) != 0) {
+                        gameAppSize += blamedSize;
+                        break;
+                    }
+                    otherAppsSize += blamedSize;
+                    break;
+            }
+        }
+
+        AppsStorageResult result = new AppsStorageResult(gameAppSize, musicAppsSize, photosAppsSize,
+                videoAppsSize, otherAppsSize);
+
+        LOG.d("Loading external stats");
+        try {
+            result.mStorageStats = mStatsManager.getExternalStorageStats(null,
+                    UserHandle.of(userId));
+        } catch (IOException e) {
+            LOG.w("External stats not loaded" + e);
+        }
+        LOG.d("Obtaining result completed");
+        return result;
+    }
+
+    /**
+     * Class to hold the result for different categories for storage.
+     */
+    public static class AppsStorageResult {
+        private final long mGamesSize;
+        private final long mMusicAppsSize;
+        private final long mPhotosAppsSize;
+        private final long mVideoAppsSize;
+        private final long mOtherAppsSize;
+        private long mCacheSize;
+        private StorageStatsSource.ExternalStorageStats mStorageStats;
+
+        AppsStorageResult(long gamesSize, long musicAppsSize, long photosAppsSize,
+                long videoAppsSize, long otherAppsSize) {
+            mGamesSize = gamesSize;
+            mMusicAppsSize = musicAppsSize;
+            mPhotosAppsSize = photosAppsSize;
+            mVideoAppsSize = videoAppsSize;
+            mOtherAppsSize = otherAppsSize;
+        }
+
+        /**
+         * Returns the size in bytes used by the applications of category {@link CATEGORY_GAME}.
+         */
+        public long getGamesSize() {
+            return mGamesSize;
+        }
+
+        /**
+         * Returns the size in bytes used by the applications of category {@link CATEGORY_AUDIO}.
+         */
+        public long getMusicAppsSize() {
+            return mMusicAppsSize;
+        }
+
+        /**
+         * Returns the size in bytes used by the applications of category {@link CATEGORY_IMAGE}.
+         */
+        public long getPhotosAppsSize() {
+            return mPhotosAppsSize;
+        }
+
+        /**
+         * Returns the size in bytes used by the applications of category {@link CATEGORY_VIDEO}.
+         */
+        public long getVideoAppsSize() {
+            return mVideoAppsSize;
+        }
+
+        /**
+         * Returns the size in bytes used by the applications not assigned to one of the other
+         * categories.
+         */
+        public long getOtherAppsSize() {
+            return mOtherAppsSize;
+        }
+
+        /**
+         * Returns the cached size in bytes.
+         */
+        public long getCacheSize() {
+            return mCacheSize;
+        }
+
+        /**
+         * Sets the storage cached size.
+         */
+        public void setCacheSize(long cacheSize) {
+            this.mCacheSize = cacheSize;
+        }
+
+        /**
+         * Returns the size in bytes for external storage of mounted device.
+         */
+        public StorageStatsSource.ExternalStorageStats getExternalStats() {
+            return mStorageStats;
+        }
+
+        /**
+         * Sets the size in bytes for the external storage.
+         */
+        public void setExternalStats(
+                StorageStatsSource.ExternalStorageStats externalStats) {
+            this.mStorageStats = externalStats;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageFileCategoryPreferenceController.java b/src/com/android/car/settings/storage/StorageFileCategoryPreferenceController.java
new file mode 100644
index 0000000..16e0c3c
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageFileCategoryPreferenceController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.util.SparseArray;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.ProgressBarPreference;
+import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+/**
+ * Controller which determines the storage for file category in the storage preference screen.
+ */
+public class StorageFileCategoryPreferenceController extends StorageUsageBasePreferenceController {
+
+    private StorageVolumeProvider mStorageVolumeProvider;
+
+    public StorageFileCategoryPreferenceController(Context context,
+            String preferenceKey, FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        StorageManager sm = context.getSystemService(StorageManager.class);
+        mStorageVolumeProvider = new StorageManagerVolumeProvider(sm);
+    }
+
+    @Override
+    protected long calculateCategoryUsage(SparseArray<StorageAsyncLoader.AppsStorageResult> result,
+            long usedSizeBytes) {
+        StorageAsyncLoader.AppsStorageResult data = result.get(
+                getCarUserManagerHelper().getCurrentProcessUserId());
+        return data.getExternalStats().totalBytes - data.getExternalStats().audioBytes
+                - data.getExternalStats().videoBytes - data.getExternalStats().imageBytes
+                - data.getExternalStats().appBytes;
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(ProgressBarPreference preference) {
+        Intent intent = getFilesIntent();
+        intent.putExtra(Intent.EXTRA_USER_ID, getCarUserManagerHelper().getCurrentProcessUserId());
+        getContext().startActivityAsUser(intent,
+                new UserHandle(getCarUserManagerHelper().getCurrentProcessUserId()));
+        return true;
+    }
+
+    private Intent getFilesIntent() {
+        return mStorageVolumeProvider.findEmulatedForPrivate(getVolumeInfo()).buildBrowseIntent();
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageMediaCategoryDetailFragment.java b/src/com/android/car/settings/storage/StorageMediaCategoryDetailFragment.java
new file mode 100644
index 0000000..66919d1
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageMediaCategoryDetailFragment.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.storage;
+
+
+import static com.android.car.settings.storage.StorageMediaCategoryPreferenceController.EXTRA_AUDIO_BYTES;
+import static com.android.car.settings.storage.StorageUtils.maybeInitializeVolume;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+import com.android.car.settings.R;
+import com.android.car.settings.applications.ApplicationListItemManager;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.applications.ApplicationsState;
+
+/**
+ * Lists all installed applications with category audio and their summary.
+ */
+public class StorageMediaCategoryDetailFragment extends SettingsFragment {
+
+    private ApplicationListItemManager mAppListItemManager;
+
+    /**
+     * Gets the instance of this class.
+     */
+    public static StorageMediaCategoryDetailFragment getInstance() {
+        StorageMediaCategoryDetailFragment storageMedia = new StorageMediaCategoryDetailFragment();
+        return storageMedia;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.storage_media_category_detail_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        Bundle bundle = getArguments();
+        long externalAudioBytes = bundle.getLong(EXTRA_AUDIO_BYTES);
+        StorageManager sm = context.getSystemService(StorageManager.class);
+        VolumeInfo volume = maybeInitializeVolume(sm, getArguments());
+        Application application = requireActivity().getApplication();
+        mAppListItemManager = new ApplicationListItemManager(volume, getLifecycle(),
+                ApplicationsState.getInstance(application));
+        StorageMediaCategoryDetailPreferenceController pc = use(
+                StorageMediaCategoryDetailPreferenceController.class,
+                R.string.pk_storage_music_audio_details);
+        mAppListItemManager.registerListener(pc);
+        pc.setExternalAudioBytes(externalAudioBytes);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAppListItemManager.startLoading(ApplicationsState.FILTER_AUDIO,
+                ApplicationsState.SIZE_COMPARATOR);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mAppListItemManager.onFragmentStart();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mAppListItemManager.onFragmentStop();
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageMediaCategoryDetailPreferenceController.java b/src/com/android/car/settings/storage/StorageMediaCategoryDetailPreferenceController.java
new file mode 100644
index 0000000..548faa2
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageMediaCategoryDetailPreferenceController.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.ArrayList;
+
+/**
+ * Controller extends the {@link StorageApplicationListPreferenceController} which adds all the
+ * application in the parent controller and in addition one more application specific for listing
+ * audio files.
+ */
+public class StorageMediaCategoryDetailPreferenceController extends
+        StorageApplicationListPreferenceController {
+
+    private long mExternalAudioBytes;
+    private boolean mIsStorageAudioPreferenceAdded;
+
+    public StorageMediaCategoryDetailPreferenceController(Context context,
+            String preferenceKey, FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    public void onDataLoaded(ArrayList<ApplicationsState.AppEntry> apps) {
+        super.onDataLoaded(apps);
+        if (mIsStorageAudioPreferenceAdded) {
+            return;
+        }
+        Preference preference = createPreference(
+                getContext().getString(R.string.storage_audio_files_title),
+                Long.toString(mExternalAudioBytes),
+                getContext().getDrawable(R.drawable.ic_headset), /* key= */ null);
+        // remove the onClickListener which was set above with null key. This preference should
+        // do nothing on click.
+        preference.setOnPreferenceClickListener(null);
+        getPreference().addPreference(preference);
+        mIsStorageAudioPreferenceAdded = true;
+    }
+
+    /**
+     * Sets the external audio bytes
+     */
+    public void setExternalAudioBytes(long externalAudioBytes) {
+        mExternalAudioBytes = externalAudioBytes;
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageMediaCategoryPreferenceController.java b/src/com/android/car/settings/storage/StorageMediaCategoryPreferenceController.java
new file mode 100644
index 0000000..1766be3
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageMediaCategoryPreferenceController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseArray;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.ProgressBarPreference;
+
+/**
+ * Controller which determines the storage for media category in the storage preference screen.
+ */
+public class StorageMediaCategoryPreferenceController extends StorageUsageBasePreferenceController {
+
+    public static final String EXTRA_AUDIO_BYTES = "extra_audio_bytes";
+
+    private long mExternalAudioBytes;
+
+    public StorageMediaCategoryPreferenceController(Context context,
+            String preferenceKey, FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected long calculateCategoryUsage(SparseArray<StorageAsyncLoader.AppsStorageResult> result,
+            long usedSizeBytes) {
+        StorageAsyncLoader.AppsStorageResult data = result.get(
+                getCarUserManagerHelper().getCurrentProcessUserId());
+        mExternalAudioBytes = data.getExternalStats().audioBytes;
+        return data.getMusicAppsSize() + mExternalAudioBytes;
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(ProgressBarPreference preference) {
+        Bundle bundle = new Bundle();
+        bundle.putLong(EXTRA_AUDIO_BYTES, mExternalAudioBytes);
+        StorageMediaCategoryDetailFragment storageMediaCategoryDetailFragment =
+                StorageMediaCategoryDetailFragment.getInstance();
+        storageMediaCategoryDetailFragment.setArguments(bundle);
+        getFragmentController().launchFragment(storageMediaCategoryDetailFragment);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageOtherCategoryDetailFragment.java b/src/com/android/car/settings/storage/StorageOtherCategoryDetailFragment.java
new file mode 100644
index 0000000..ac71912
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageOtherCategoryDetailFragment.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.storage;
+
+import static com.android.car.settings.storage.StorageUtils.maybeInitializeVolume;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+import com.android.car.settings.R;
+import com.android.car.settings.applications.ApplicationListItemManager;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.applications.ApplicationsState;
+
+/**
+ * Lists all installed applications with no category defined and their summary.
+ */
+public class StorageOtherCategoryDetailFragment extends SettingsFragment {
+
+    private ApplicationListItemManager mAppListItemManager;
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.storage_other_category_detail_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        Application application = requireActivity().getApplication();
+        StorageManager sm = context.getSystemService(StorageManager.class);
+        VolumeInfo volume = maybeInitializeVolume(sm, getArguments());
+        mAppListItemManager = new ApplicationListItemManager(volume, getLifecycle(),
+                ApplicationsState.getInstance(application));
+        mAppListItemManager.registerListener(
+                use(StorageApplicationListPreferenceController.class,
+                        R.string.pk_storage_other_apps_details));
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAppListItemManager.startLoading(ApplicationsState.FILTER_OTHER_APPS,
+                ApplicationsState.SIZE_COMPARATOR);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mAppListItemManager.onFragmentStart();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mAppListItemManager.onFragmentStop();
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageOtherCategoryPreferenceController.java b/src/com/android/car/settings/storage/StorageOtherCategoryPreferenceController.java
new file mode 100644
index 0000000..09e8814
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageOtherCategoryPreferenceController.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.util.SparseArray;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Controller which determines the storage for the applications not assigned to one of the other
+ * categories in the storage preference screen.
+ */
+public class StorageOtherCategoryPreferenceController extends StorageUsageBasePreferenceController {
+
+    public StorageOtherCategoryPreferenceController(Context context,
+            String preferenceKey, FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected long calculateCategoryUsage(SparseArray<StorageAsyncLoader.AppsStorageResult> result,
+            long usedSizeBytes) {
+        StorageAsyncLoader.AppsStorageResult data = result.get(
+                getCarUserManagerHelper().getCurrentProcessUserId());
+        return data.getOtherAppsSize();
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageSettingsFragment.java b/src/com/android/car/settings/storage/StorageSettingsFragment.java
new file mode 100644
index 0000000..efdc487
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageSettingsFragment.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.android.car.settings.storage.StorageUtils.maybeInitializeVolume;
+
+import android.annotation.XmlRes;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+import androidx.loader.app.LoaderManager;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Fragment which shows the settings for storage. */
+public class StorageSettingsFragment extends SettingsFragment {
+
+    private StorageSettingsManager mStorageSettingsManager;
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.storage_settings_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        StorageManager sm = context.getSystemService(StorageManager.class);
+        VolumeInfo volume = maybeInitializeVolume(sm, getArguments());
+        mStorageSettingsManager = new StorageSettingsManager(getContext(), volume);
+        List<StorageUsageBasePreferenceController> usagePreferenceControllers =
+                Arrays.asList(
+                        use(StorageMediaCategoryPreferenceController.class,
+                                R.string.pk_storage_music_audio),
+                        use(StorageOtherCategoryPreferenceController.class,
+                                R.string.pk_storage_other_apps),
+                        use(StorageFileCategoryPreferenceController.class,
+                                R.string.pk_storage_files),
+                        use(StorageSystemCategoryPreferenceController.class,
+                                R.string.pk_storage_system));
+
+        for (StorageUsageBasePreferenceController pc : usagePreferenceControllers) {
+            mStorageSettingsManager.registerListener(pc);
+            pc.setVolumeInfo(volume);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        LoaderManager loaderManager = LoaderManager.getInstance(this);
+        mStorageSettingsManager.startLoading(loaderManager);
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageSettingsManager.java b/src/com/android/car/settings/storage/StorageSettingsManager.java
new file mode 100644
index 0000000..d058a01
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageSettingsManager.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.usage.StorageStatsManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.SparseArray;
+
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+
+import com.android.settingslib.applications.StorageStatsSource;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to manage all the callbacks needed to calculate the total volume, storage used by each app
+ * category and notifying the listeners when the data is loaded.
+ */
+public class StorageSettingsManager {
+
+    /**
+     * Callback that is called once the volume of data is loaded for the mounted device.
+     */
+    public interface VolumeListener {
+        /**
+         * Called when the data is successfully loaded from {@link VolumeSizeCallback} and the
+         * total and used size for the mounted device is calculated from {@link AppsStorageResult}
+         */
+        void onDataLoaded(SparseArray<StorageAsyncLoader.AppsStorageResult> result,
+                long usedSizeBytes, long totalSizeBytes);
+    }
+
+    private static final int STORAGE_JOB_ID = 0;
+    private static final int VOLUME_SIZE_JOB_ID = 1;
+
+    private final Context mContext;
+    private final VolumeInfo mVolumeInfo;
+
+    private List<VolumeListener> mVolumeListeners = new ArrayList<>();
+    private PrivateStorageInfo mPrivateStorageInfo;
+    private SparseArray<StorageAsyncLoader.AppsStorageResult> mAppsStorageResultSparseArray;
+
+    StorageSettingsManager(Context context, VolumeInfo volume) {
+        mContext = context;
+        mVolumeInfo = volume;
+    }
+
+    /**
+     * Registers a listener that will be notified once the data is loaded.
+     */
+    public void registerListener(VolumeListener volumeListener) {
+        if (!mVolumeListeners.contains(volumeListener)) {
+            mVolumeListeners.add(volumeListener);
+        }
+    }
+
+    /**
+     * Unregisters the listener.
+     */
+    public void unregisterlistener(VolumeListener volumeListener) {
+        mVolumeListeners.remove(volumeListener);
+    }
+
+    /**
+     * Start calculating the storage and volume.
+     */
+    public void startLoading(LoaderManager loaderManager) {
+        loaderManager.restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, new AppsStorageResult());
+        loaderManager.restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallback());
+    }
+
+    private void onReceivedSizes() {
+        if (mAppsStorageResultSparseArray != null && mPrivateStorageInfo != null) {
+            long privateUsedBytes = mPrivateStorageInfo.totalBytes - mPrivateStorageInfo.freeBytes;
+            for (VolumeListener listener : mVolumeListeners) {
+                listener.onDataLoaded(mAppsStorageResultSparseArray, privateUsedBytes,
+                        mPrivateStorageInfo.totalBytes);
+            }
+        }
+    }
+
+    /**
+     * Callback to get the storage volume information for the device that is mounted.
+     */
+    private class VolumeSizeCallback
+            implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> {
+
+        @Override
+        public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) {
+            StorageManager sm = mContext.getSystemService(StorageManager.class);
+            StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(sm);
+            StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
+            return new VolumeSizesLoader(mContext, smvp, stats, mVolumeInfo);
+        }
+
+        @Override
+        public void onLoadFinished(
+                Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) {
+            if (privateStorageInfo == null) {
+                return;
+            }
+            mPrivateStorageInfo = privateStorageInfo;
+            onReceivedSizes();
+        }
+
+        @Override
+        public void onLoaderReset(Loader<PrivateStorageInfo> loader) {
+        }
+    }
+
+    /**
+     * Callback to calculate how much space each category of applications is using.
+     */
+    private class AppsStorageResult implements
+            LoaderManager.LoaderCallbacks<SparseArray<StorageAsyncLoader.AppsStorageResult>> {
+
+        @NonNull
+        @Override
+        public Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> onCreateLoader(int id,
+                @Nullable Bundle args) {
+            return new StorageAsyncLoader(mContext, new CarUserManagerHelper(mContext),
+                    new StorageStatsSource(mContext));
+        }
+
+        @Override
+        public void onLoadFinished(
+                @NonNull Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader,
+                SparseArray<StorageAsyncLoader.AppsStorageResult> data) {
+            mAppsStorageResultSparseArray = data;
+            onReceivedSizes();
+        }
+
+        @Override
+        public void onLoaderReset(
+                @NonNull Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader) {
+        }
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageSizeBasePreferenceController.java b/src/com/android/car/settings/storage/StorageSizeBasePreferenceController.java
new file mode 100644
index 0000000..77ecbb4
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageSizeBasePreferenceController.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.text.format.Formatter;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.applications.StorageStatsSource;
+
+/**
+ * Controller which have the basic logic to determines the storage size details for a particular
+ * application.
+ */
+public abstract class StorageSizeBasePreferenceController extends
+        PreferenceController<StorageAppDetailPreference> implements
+        AppsStorageStatsManager.Callback {
+
+    private StorageStatsSource.AppStorageStats mAppStorageStats;
+    private AppsStorageStatsManager mAppsStorageStatsManager;
+    private Context mContext;
+    private boolean mDataCleared = false;
+    private boolean mCachedCleared = false;
+
+    public StorageSizeBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mContext = context;
+    }
+
+    @Override
+    protected Class<StorageAppDetailPreference> getPreferenceType() {
+        return StorageAppDetailPreference.class;
+    }
+
+    /**
+     * Calculates the storage size for a application.
+     *
+     * @return size value in bytes.
+     */
+    protected abstract long getSize();
+
+    @Override
+    protected void onCreateInternal() {
+        if (mAppsStorageStatsManager == null) {
+            return;
+        }
+        mAppsStorageStatsManager.registerListener(this);
+    }
+
+    /**
+     * Sets the {@link AppsStorageStatsManager} which will be used to register the controller to the
+     * Listener {@link AppsStorageStatsManager.Callback}.
+     */
+    public void setAppsStorageStatsManager(AppsStorageStatsManager appsStorageStatsManager) {
+        mAppsStorageStatsManager = appsStorageStatsManager;
+    }
+
+    @Override
+    protected void updateState(StorageAppDetailPreference preference) {
+        if (mAppStorageStats == null) {
+            return;
+        }
+        preference.setDetailText(getSizeStr(getSize()));
+    }
+
+    /**
+     * Sets the {@link StorageStatsSource.AppStorageStats} for a particular application.
+     */
+    public void setAppStorageStats(StorageStatsSource.AppStorageStats appStorageStats) {
+        mAppStorageStats = appStorageStats;
+    }
+
+    /**
+     * Gets the {@link StorageStatsSource.AppStorageStats} for a particular application.
+     */
+    public StorageStatsSource.AppStorageStats getAppStorageStats() {
+        return mAppStorageStats;
+    }
+
+    boolean isCachedCleared() {
+        return mCachedCleared;
+    }
+
+    boolean isDataCleared() {
+        return mDataCleared;
+    }
+
+    private String getSizeStr(long size) {
+        return Formatter.formatFileSize(mContext, size);
+    }
+
+    @Override
+    public void onDataLoaded(StorageStatsSource.AppStorageStats data, boolean cacheCleared,
+            boolean dataCleared) {
+        //  Sets if user have cleared the cache and should zero the cache bytes.
+        //  When the cache is cleared, the cache directories are recreated. These directories have
+        //  some size, but are empty. We zero this out to best match user expectations.
+        mCachedCleared = cacheCleared;
+
+        //  Sets if user have cleared data and should zero the data bytes.
+        //  When the data is cleared, the directory are recreated. Directories have some size,
+        //  but are empty. We zero this out to best match user expectations.
+        mDataCleared = dataCleared;
+        refreshUi();
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageSystemCategoryPreferenceController.java b/src/com/android/car/settings/storage/StorageSystemCategoryPreferenceController.java
new file mode 100644
index 0000000..2f0d23f
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageSystemCategoryPreferenceController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.app.AlertDialog;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.TrafficStats;
+import android.os.Build;
+import android.util.SparseArray;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.ProgressBarPreference;
+
+/**
+ * Controller which determines the storage for system category in the storage preference screen.
+ */
+public class StorageSystemCategoryPreferenceController extends
+        StorageUsageBasePreferenceController {
+
+    public StorageSystemCategoryPreferenceController(Context context,
+            String preferenceKey, FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected long calculateCategoryUsage(
+            SparseArray<StorageAsyncLoader.AppsStorageResult> result, long usedSizeBytes) {
+        long attributedSize = 0;
+        for (int i = 0; i < result.size(); i++) {
+            StorageAsyncLoader.AppsStorageResult otherData = result.valueAt(i);
+
+            attributedSize += otherData.getGamesSize()
+                    + otherData.getMusicAppsSize()
+                    + otherData.getVideoAppsSize()
+                    + otherData.getPhotosAppsSize()
+                    + otherData.getOtherAppsSize();
+
+            attributedSize += otherData.getExternalStats().totalBytes
+                    - otherData.getExternalStats().appBytes;
+        }
+        return Math.max(TrafficStats.GB_IN_BYTES, usedSizeBytes - attributedSize);
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(ProgressBarPreference preference) {
+        AlertDialog alertDialog = new AlertDialog.Builder(getContext())
+                .setMessage(getContext().getString(R.string.storage_detail_dialog_system,
+                        Build.VERSION.RELEASE))
+                .setPositiveButton(android.R.string.ok, null)
+                .create();
+        alertDialog.show();
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageUsageBasePreferenceController.java b/src/com/android/car/settings/storage/StorageUsageBasePreferenceController.java
new file mode 100644
index 0000000..93fcb74
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageUsageBasePreferenceController.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.storage.VolumeInfo;
+import android.util.SparseArray;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.ProgressBarPreference;
+
+/**
+ * Controller which have the basic logic to determines the storage for different categories visible
+ * in the storage preference screen.
+ */
+public abstract class StorageUsageBasePreferenceController extends
+        PreferenceController<ProgressBarPreference> implements
+        StorageSettingsManager.VolumeListener {
+
+    private static final int PROGRESS_MAX = 100;
+
+    private VolumeInfo mVolumeInfo;
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    public StorageUsageBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<ProgressBarPreference> getPreferenceType() {
+        return ProgressBarPreference.class;
+    }
+
+    /**
+     * Calculates the storage used by the category.
+     *
+     * @return usage value in bytes.
+     */
+    protected abstract long calculateCategoryUsage(
+            SparseArray<StorageAsyncLoader.AppsStorageResult> result, long usedSizeBytes);
+
+    @Override
+    protected void onCreateInternal() {
+        getPreference().setSummary(R.string.memory_calculating_size);
+        getPreference().setMax(PROGRESS_MAX);
+    }
+
+    @Override
+    public void onDataLoaded(SparseArray<StorageAsyncLoader.AppsStorageResult> result,
+            long usedSizeBytes, long totalSizeBytes) {
+        setStorageSize(calculateCategoryUsage(result, usedSizeBytes), totalSizeBytes);
+    }
+
+    CarUserManagerHelper getCarUserManagerHelper() {
+        return mCarUserManagerHelper;
+    }
+
+    public VolumeInfo getVolumeInfo() {
+        return mVolumeInfo;
+    }
+
+    public void setVolumeInfo(VolumeInfo volumeInfo) {
+        mVolumeInfo = volumeInfo;
+    }
+
+    /**
+     * Sets the storage size for this preference that will be displayed as a summary. It will also
+     * update the progress bar accordingly.
+     */
+    private void setStorageSize(long size, long total) {
+        getPreference().setSummary(
+                FileSizeFormatter.formatFileSize(
+                        getContext(),
+                        size,
+                        getGigabyteSuffix(getContext().getResources()),
+                        FileSizeFormatter.GIGABYTE_IN_BYTES));
+        int progressPercent;
+        if (total == 0) {
+            progressPercent = 0;
+        } else {
+            progressPercent = (int) (size * PROGRESS_MAX / total);
+        }
+        getPreference().setProgress(progressPercent);
+    }
+
+    private static int getGigabyteSuffix(Resources res) {
+        return res.getIdentifier("gigabyteShort", "string", "android");
+    }
+}
diff --git a/src/com/android/car/settings/storage/StorageUtils.java b/src/com/android/car/settings/storage/StorageUtils.java
new file mode 100644
index 0000000..f393dbc
--- /dev/null
+++ b/src/com/android/car/settings/storage/StorageUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+/** Utility functions for use in storage settings. */
+public class StorageUtils {
+
+    private StorageUtils() { }
+
+    /**
+     * Tries to initialize a volume with the given bundle. If it is a valid, private, and readable
+     * {@link VolumeInfo}, it is returned. If it is not valid, {@code null} is returned.
+     */
+    @Nullable
+    public static VolumeInfo maybeInitializeVolume(StorageManager sm, @Nullable Bundle bundle) {
+        String volumeId = VolumeInfo.ID_PRIVATE_INTERNAL;
+        if (bundle != null) {
+            volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID,
+                    VolumeInfo.ID_PRIVATE_INTERNAL);
+        }
+
+        VolumeInfo volume = sm.findVolumeById(volumeId);
+        return isVolumeValid(volume) ? volume : null;
+    }
+
+    private static boolean isVolumeValid(VolumeInfo volume) {
+        return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE)
+                && volume.isMountedReadable();
+    }
+}
diff --git a/src/com/android/car/settings/storage/VolumeSizesLoader.java b/src/com/android/car/settings/storage/VolumeSizesLoader.java
new file mode 100644
index 0000000..f207cbc
--- /dev/null
+++ b/src/com/android/car/settings/storage/VolumeSizesLoader.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.os.storage.VolumeInfo;
+
+import com.android.car.settingslib.loader.AsyncLoader;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import java.io.IOException;
+
+/**
+ * Loads the storage volume information for the device mounted.
+ */
+public class VolumeSizesLoader extends AsyncLoader<PrivateStorageInfo> {
+    private final StorageVolumeProvider mVolumeProvider;
+    private final StorageStatsManager mStats;
+    private final VolumeInfo mVolume;
+
+    public VolumeSizesLoader(Context context, StorageVolumeProvider volumeProvider,
+            StorageStatsManager stats, VolumeInfo volume) {
+        super(context);
+        mVolumeProvider = volumeProvider;
+        mStats = stats;
+        mVolume = volume;
+    }
+
+    @Override
+    public PrivateStorageInfo loadInBackground() {
+        PrivateStorageInfo volumeSizes;
+        try {
+            volumeSizes = getVolumeSize();
+        } catch (IOException e) {
+            return null;
+        }
+        return volumeSizes;
+    }
+
+    private PrivateStorageInfo getVolumeSize() throws IOException {
+        long privateTotalBytes = mVolumeProvider.getTotalBytes(mStats, mVolume);
+        long privateFreeBytes = mVolumeProvider.getFreeBytes(mStats, mVolume);
+        return new PrivateStorageInfo(privateFreeBytes, privateTotalBytes);
+    }
+}
diff --git a/src/com/android/car/settings/suggestions/SettingsSuggestionsController.java b/src/com/android/car/settings/suggestions/SettingsSuggestionsController.java
deleted file mode 100644
index 10e6808..0000000
--- a/src/com/android/car/settings/suggestions/SettingsSuggestionsController.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.suggestions;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.service.settings.suggestions.Suggestion;
-import android.support.annotation.StringRes;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-
-import com.android.car.list.TypedPagedListAdapter;
-import com.android.car.settings.R;
-import com.android.car.settings.common.Logger;
-import com.android.settingslib.suggestions.SuggestionController;
-import com.android.settingslib.suggestions.SuggestionControllerMixin;
-import com.android.settingslib.suggestions.SuggestionLoader;
-import com.android.settingslib.utils.IconCache;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Retrieves suggestions and prepares them for rendering.
- * Modeled after {@link SuggestionControllerMixin}, differs by implementing support library version
- * of LoaderManager and Loader. Does not implement use of LifeCycle.
- */
-public class SettingsSuggestionsController implements
-        SuggestionController.ServiceConnectionListener,
-        LoaderManager.LoaderCallbacks<List<Suggestion>> {
-    private static final Logger LOG = new Logger(SettingsSuggestionsController.class);
-    private static final ComponentName mComponentName = new ComponentName(
-            "com.android.settings.intelligence",
-            "com.android.settings.intelligence.suggestions.SuggestionService");
-    // These values are hard coded until we receive the OK to plumb them through
-    // SettingsIntelligence. This is ok as right now only SUW uses this framework.
-    @StringRes
-    private static final int PRIMARY_ACTION_ID = R.string.suggestion_primary_button;
-    @StringRes
-    private static final int SECONDARY_ACTION_ID = R.string.suggestion_secondary_button;
-
-    private final Context mContext;
-    private final LoaderManager mLoaderManager;
-    private final Listener mListener;
-    private final SuggestionController mSuggestionController;
-    private final IconCache mIconCache;
-
-    public SettingsSuggestionsController(
-            Context context,
-            LoaderManager loaderManager,
-            @NonNull Listener listener) {
-        mContext = context;
-        mLoaderManager = loaderManager;
-        mListener = listener;
-        mIconCache = new IconCache(context);
-        mSuggestionController = new SuggestionController(
-                mContext,
-                mComponentName,
-                /* listener= */ this);
-    }
-
-    @Override
-    public void onServiceConnected() {
-        LOG.v("onServiceConnected");
-        mLoaderManager.restartLoader(
-                SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS,
-                /* args= */ null,
-                /* callback= */ this);
-    }
-
-    @Override
-    public void onServiceDisconnected() {
-        LOG.v("onServiceDisconnected");
-        cleanupLoader();
-    }
-
-    @NonNull
-    @Override
-    public Loader<List<Suggestion>> onCreateLoader(int id, @Nullable Bundle args) {
-        LOG.v("onCreateLoader: " + id);
-        if (id == SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS) {
-            return new SettingsSuggestionsLoader(mContext, mSuggestionController);
-        }
-        throw new IllegalArgumentException("This loader id is not supported " + id);
-    }
-
-    @Override
-    public void onLoadFinished(
-            @NonNull Loader<List<Suggestion>> loader,
-            List<Suggestion> suggestionList) {
-        LOG.v("onLoadFinished");
-        if (suggestionList == null) {
-            return;
-        }
-        ArrayList<TypedPagedListAdapter.LineItem> items = new ArrayList<>();
-        for (final Suggestion suggestion : suggestionList) {
-            LOG.v("Suggestion ID: " + suggestion.getId());
-            Drawable itemIcon = mIconCache.getIcon(suggestion.getIcon());
-            SuggestionLineItem suggestionLineItem =
-                    new SuggestionLineItem(
-                            suggestion.getTitle(),
-                            suggestion.getSummary(),
-                            itemIcon,
-                            mContext.getString(PRIMARY_ACTION_ID),
-                            mContext.getString(SECONDARY_ACTION_ID),
-                            v -> {
-                                try {
-                                    suggestion.getPendingIntent().send();
-                                    launchSuggestion(suggestion);
-                                } catch (PendingIntent.CanceledException e) {
-                                    LOG.w("Failed to start suggestion " + suggestion.getTitle());
-                                }
-                            },
-                            adapterPosition -> {
-                                dismissSuggestion(suggestion);
-                                mListener.onSuggestionDismissed(adapterPosition);
-
-                            });
-            items.add(suggestionLineItem);
-        }
-        mListener.onSuggestionsLoaded(items);
-    }
-
-    @Override
-    public void onLoaderReset(@NonNull Loader<List<Suggestion>> loader) {
-        LOG.v("onLoaderReset");
-    }
-
-    /**
-     * Start the suggestions controller.
-     */
-    public void start() {
-        LOG.v("Start");
-        mSuggestionController.start();
-    }
-
-    /**
-     * Stop the suggestions controller.
-     */
-    public void stop() {
-        LOG.v("Stop");
-        mSuggestionController.stop();
-        cleanupLoader();
-
-    }
-
-    private void cleanupLoader() {
-        LOG.v("cleanupLoader");
-        mLoaderManager.destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS);
-    }
-
-    private void dismissSuggestion(Suggestion suggestion) {
-        LOG.v("dismissSuggestion");
-        mSuggestionController.dismissSuggestions(suggestion);
-    }
-
-    private void launchSuggestion(Suggestion suggestion) {
-        LOG.v("launchSuggestion");
-        mSuggestionController.launchSuggestion(suggestion);
-    }
-
-    /**
-     * Listener interface to notify of data state changes and actions.
-     */
-    public interface Listener {
-        /**
-         * Invoked when deferred setup items have been loaded.
-         *
-         * @param suggestions List of deferred setup suggestions.
-         */
-        void onSuggestionsLoaded(@NonNull ArrayList<TypedPagedListAdapter.LineItem> suggestions);
-
-        /***
-         * Invoked when a suggestion is dismissed.
-         *
-         * @param adapterPosition the position of the suggestion item in it's adapter.
-         */
-        void onSuggestionDismissed(int adapterPosition);
-    }
-}
diff --git a/src/com/android/car/settings/suggestions/SettingsSuggestionsLoader.java b/src/com/android/car/settings/suggestions/SettingsSuggestionsLoader.java
index 33c4c0f..5bd014d 100644
--- a/src/com/android/car/settings/suggestions/SettingsSuggestionsLoader.java
+++ b/src/com/android/car/settings/suggestions/SettingsSuggestionsLoader.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 import android.service.settings.suggestions.Suggestion;
 
-import com.android.car.settingslib.loader.AsyncLoader;
+import androidx.annotation.Nullable;
+import androidx.loader.content.AsyncTaskLoader;
+
 import com.android.car.settings.common.Logger;
 import com.android.settingslib.suggestions.SuggestionController;
 
@@ -30,7 +32,7 @@
  * {@link com.android.settingslib.suggestions.SuggestionLoader}, only change is to extend from car
  * settings {@link AsyncLoader} which extends from support library {@link AsyncTaskLoader}.
  */
-public class SettingsSuggestionsLoader extends AsyncLoader<List<Suggestion>> {
+public class SettingsSuggestionsLoader extends AsyncTaskLoader<List<Suggestion>> {
     private static final Logger LOG = new Logger(SettingsSuggestionsLoader.class);
 
     /**
@@ -40,6 +42,43 @@
 
     private final SuggestionController mSuggestionController;
 
+    @Nullable
+    private List<Suggestion> mResult;
+
+    @Override
+    protected void onStartLoading() {
+        if (mResult != null) {
+            deliverResult(mResult);
+        }
+
+        if (takeContentChanged() || mResult == null) {
+            forceLoad();
+        }
+    }
+
+    @Override
+    protected void onStopLoading() {
+        cancelLoad();
+    }
+
+    @Override
+    public void deliverResult(List<Suggestion> data) {
+        if (isReset()) {
+            return;
+        }
+        mResult = data;
+        if (isStarted()) {
+            super.deliverResult(data);
+        }
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+        onStopLoading();
+        mResult = null;
+    }
+
     public SettingsSuggestionsLoader(Context context, SuggestionController controller) {
         super(context);
         mSuggestionController = controller;
diff --git a/src/com/android/car/settings/suggestions/SuggestionLineItem.java b/src/com/android/car/settings/suggestions/SuggestionLineItem.java
deleted file mode 100644
index 6ad9cce..0000000
--- a/src/com/android/car/settings/suggestions/SuggestionLineItem.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.suggestions;
-
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import com.android.car.list.ActionIconButtonLineItem;
-
-/**
- * Represents suggestion list item.
- */
-public class SuggestionLineItem extends ActionIconButtonLineItem {
-
-    private final CharSequence mSummary;
-    private final View.OnClickListener mOnClickListener;
-    private final ActionListener mActionListener;
-
-    /**
-     * Creates a {@link SuggestionLineItem} with title, summary, icons, and click handlers.
-     */
-    public SuggestionLineItem(
-            CharSequence title,
-            CharSequence summary,
-            Drawable iconDrawable,
-            CharSequence primaryAction,
-            CharSequence secondaryAction,
-            View.OnClickListener onClickListener,
-            ActionListener actionListener) {
-        super(title, primaryAction, secondaryAction, iconDrawable);
-        mSummary = summary;
-        mOnClickListener = onClickListener;
-        mActionListener = actionListener;
-    }
-
-    @Override
-    public CharSequence getDesc() {
-        return mSummary;
-    }
-
-    @Override
-    public boolean isExpandable() {
-        return true;
-    }
-
-    @Override
-    public void onClick(View view) {
-        mOnClickListener.onClick(view);
-    }
-
-    @Override
-    public void onSecondaryActionButtonClick(int adapterPosition) {
-        mActionListener.onSuggestionItemDismissed(adapterPosition);
-    }
-
-    @Override
-    public void onPrimaryActionButtonClick(View view) {
-        onClick(view);
-    }
-
-    /**
-     * Interface that surfaces events on the suggestion.
-     */
-    public interface ActionListener {
-
-        /**
-         * Invoked when a suggestions item is dismissed.
-         *
-         * @param adapterPosition the position of the suggestion item in it's adapter.
-         */
-        void onSuggestionItemDismissed(int adapterPosition);
-    }
-}
diff --git a/src/com/android/car/settings/suggestions/SuggestionPreference.java b/src/com/android/car/settings/suggestions/SuggestionPreference.java
new file mode 100644
index 0000000..c299902
--- /dev/null
+++ b/src/com/android/car/settings/suggestions/SuggestionPreference.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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.car.settings.suggestions;
+
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+import android.view.View;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.R;
+
+import java.util.Objects;
+
+/**
+ * Custom preference for Suggestions.
+ */
+public class SuggestionPreference extends Preference {
+
+    public static final String SUGGESTION_PREFERENCE_KEY = "suggestion_pref_key";
+
+    /**
+     * Callback for actions performed on a suggestion preference.
+     */
+    public interface Callback {
+        /** Called when the suggestion should be launched. */
+        void launchSuggestion(SuggestionPreference preference);
+
+        /** Called when the suggestion should be dismissed. */
+        void dismissSuggestion(SuggestionPreference preference);
+    }
+
+    private final Callback mCallback;
+    private Suggestion mSuggestion;
+
+    public SuggestionPreference(Context context, Suggestion suggestion, Callback callback) {
+        super(context);
+        setLayoutResource(R.layout.suggestion_preference);
+        mCallback = callback;
+        setKey(SUGGESTION_PREFERENCE_KEY + suggestion.getId());
+        updateSuggestion(suggestion);
+    }
+
+    /**
+     * Returns the {@link Suggestion} represented by this preference.
+     */
+    public Suggestion getSuggestion() {
+        return mSuggestion;
+    }
+
+    /**
+     * Updates the icon, title, and summary of the preference with those of the given
+     * {@link Suggestion}.
+     *
+     * @param suggestion the updated suggestion to represent
+     * @throws IllegalArgumentException if the given suggestion has a different id than the
+     *         suggestion passed to the constructor
+     */
+    public void updateSuggestion(Suggestion suggestion) {
+        if (mSuggestion != null && !Objects.equals(mSuggestion.getId(), suggestion.getId())) {
+            throw new IllegalArgumentException(
+                    "Suggestion preference update must have a matching id");
+        }
+        mSuggestion = suggestion;
+        setIcon((suggestion.getIcon() != null) ? suggestion.getIcon().loadDrawable(getContext())
+                : null);
+        setTitle(suggestion.getTitle());
+        setSummary(suggestion.getSummary());
+    }
+
+    @Override
+    public void onBindViewHolder(final PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        holder.setDividerAllowedAbove(false);
+        holder.setDividerAllowedBelow(false);
+
+        holder.itemView.setOnClickListener(v -> mCallback.launchSuggestion(this));
+
+        View dismissButton = holder.findViewById(R.id.dismiss_button);
+        dismissButton.setOnClickListener(v -> mCallback.dismissSuggestion(this));
+    }
+}
diff --git a/src/com/android/car/settings/suggestions/SuggestionsPreferenceController.java b/src/com/android/car/settings/suggestions/SuggestionsPreferenceController.java
new file mode 100644
index 0000000..1a86590
--- /dev/null
+++ b/src/com/android/car/settings/suggestions/SuggestionsPreferenceController.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2018 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.car.settings.suggestions;
+
+import android.app.PendingIntent;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.suggestions.SuggestionController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Injects {@link SuggestionPreference} instances loaded from the SuggestionService at the
+ * location in the hierarchy of the controller's placeholder preference. The placeholder should
+ * be a {@link PreferenceGroup} which sets the controller attribute to the fully qualified name
+ * of this class.
+ *
+ * <p>For example:
+ * <pre>{@code
+ * <PreferenceCategory
+ *     android:key="@string/pk_suggestions"
+ *     android:title="@string/suggestions_title"
+ *     settings:controller="com.android.settings.suggestions.SuggestionsPreferenceController"/>
+ * }</pre>
+ */
+public class SuggestionsPreferenceController extends
+        PreferenceController<PreferenceGroup> implements
+        SuggestionController.ServiceConnectionListener,
+        LoaderManager.LoaderCallbacks<List<Suggestion>>, SuggestionPreference.Callback {
+
+    private static final Logger LOG = new Logger(SuggestionsPreferenceController.class);
+
+    // These values are hard coded until we receive the OK to plumb them through
+    // SettingsIntelligence. This is ok as right now only SUW uses this framework.
+    private static final ComponentName COMPONENT_NAME = new ComponentName(
+            "com.android.settings.intelligence",
+            "com.android.settings.intelligence.suggestions.SuggestionService");
+
+    private final SuggestionController mSuggestionController;
+    private List<Suggestion> mSuggestionsList = new ArrayList<>();
+    private LoaderManager mLoaderManager;
+
+    public SuggestionsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mSuggestionController = new SuggestionController(context,
+                COMPONENT_NAME, /* serviceConnectionListener= */ this);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /**
+     * Sets the {@link LoaderManager} used to load suggestions.
+     */
+    public void setLoaderManager(LoaderManager loaderManager) {
+        mLoaderManager = loaderManager;
+    }
+
+    /**
+     * Verifies that the controller was properly initialized with
+     * {@link #setLoaderManager(LoaderManager)}.
+     *
+     * @throws IllegalStateException if the loader manager is {@code null}
+     */
+    @Override
+    protected void checkInitialized() {
+        LOG.v("checkInitialized");
+        if (mLoaderManager == null) {
+            throw new IllegalStateException(
+                    "SuggestionPreferenceController must be initialized by calling "
+                            + "setLoaderManager(LoaderManager)");
+        }
+    }
+
+    /** Starts the suggestions controller. */
+    @Override
+    protected void onStartInternal() {
+        LOG.v("onStartInternal");
+        mSuggestionController.start();
+    }
+
+    /** Stops the suggestions controller. */
+    @Override
+    protected void onStopInternal() {
+        LOG.v("onStopInternal");
+        mSuggestionController.stop();
+        cleanupLoader();
+    }
+
+    @Override
+    public void onServiceConnected() {
+        LOG.v("onServiceConnected");
+        mLoaderManager.restartLoader(SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS, /* args= */
+                null, /* callback= */ this);
+    }
+
+    @Override
+    public void onServiceDisconnected() {
+        LOG.v("onServiceDisconnected");
+        cleanupLoader();
+    }
+
+    @NonNull
+    @Override
+    public Loader<List<Suggestion>> onCreateLoader(int id, @Nullable Bundle args) {
+        LOG.v("onCreateLoader: " + id);
+        if (id == SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS) {
+            return new SettingsSuggestionsLoader(getContext(), mSuggestionController);
+        }
+        throw new IllegalArgumentException("This loader id is not supported " + id);
+    }
+
+
+    @Override
+    public void onLoadFinished(@NonNull Loader<List<Suggestion>> loader,
+            List<Suggestion> suggestions) {
+        LOG.v("onLoadFinished");
+        if (suggestions == null) {
+            // Load started before the service was ready.
+            return;
+        }
+
+        updateSuggestionPreferences(suggestions);
+        mSuggestionsList = new ArrayList<>(suggestions);
+    }
+
+    private void updateSuggestionPreferences(List<Suggestion> suggestions) {
+        // Remove suggestions that are not in the new list.
+        for (Suggestion oldSuggestion : mSuggestionsList) {
+            boolean isInNewSuggestionList = false;
+            for (Suggestion suggestion : suggestions) {
+                if (oldSuggestion.getId().equals(suggestion.getId())) {
+                    isInNewSuggestionList = true;
+                    break;
+                }
+            }
+            if (!isInNewSuggestionList) {
+                getPreference().removePreference(
+                        getPreference().findPreference(getSuggestionPreferenceKey(oldSuggestion)));
+            }
+        }
+
+        // Add suggestions that are not in the old list and update the existing suggestions.
+        for (Suggestion suggestion : suggestions) {
+            Preference curPref = getPreference().findPreference(
+                    getSuggestionPreferenceKey(suggestion));
+            if (curPref == null) {
+                SuggestionPreference newSuggPref = new SuggestionPreference(getContext(),
+                        suggestion, /* callback= */ this);
+                getPreference().addPreference(newSuggPref);
+            } else {
+                ((SuggestionPreference) curPref).updateSuggestion(suggestion);
+            }
+        }
+
+        refreshUi();
+    }
+
+    @Override
+    public void onLoaderReset(@NonNull Loader<List<Suggestion>> loader) {
+        LOG.v("onLoaderReset");
+    }
+
+    @Override
+    public void launchSuggestion(SuggestionPreference preference) {
+        LOG.v("launchSuggestion");
+        Suggestion suggestion = preference.getSuggestion();
+        try {
+            if (suggestion.getPendingIntent() != null) {
+                suggestion.getPendingIntent().send();
+                mSuggestionController.launchSuggestion(suggestion);
+            } else {
+                LOG.w("Suggestion with null pending intent " + suggestion.getId());
+            }
+        } catch (PendingIntent.CanceledException e) {
+            LOG.w("Failed to start suggestion " + suggestion.getId());
+        }
+    }
+
+    @Override
+    public void dismissSuggestion(SuggestionPreference preference) {
+        LOG.v("dismissSuggestion");
+        Suggestion suggestion = preference.getSuggestion();
+        mSuggestionController.dismissSuggestions(suggestion);
+        mSuggestionsList.remove(suggestion);
+        getPreference().removePreference(preference);
+        refreshUi();
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        preference.setVisible(preference.getPreferenceCount() > 0);
+    }
+
+    private void cleanupLoader() {
+        LOG.v("cleanupLoader");
+        mLoaderManager.destroyLoader(SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS);
+    }
+
+    private String getSuggestionPreferenceKey(Suggestion suggestion) {
+        return SuggestionPreference.SUGGESTION_PREFERENCE_KEY + suggestion.getId();
+    }
+}
diff --git a/src/com/android/car/settings/system/AboutSettingsEntryPreferenceController.java b/src/com/android/car/settings/system/AboutSettingsEntryPreferenceController.java
new file mode 100644
index 0000000..8f13d9f
--- /dev/null
+++ b/src/com/android/car/settings/system/AboutSettingsEntryPreferenceController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Updates the about settings entry summary with the build version. */
+public class AboutSettingsEntryPreferenceController extends PreferenceController<Preference> {
+
+    public AboutSettingsEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(
+                getContext().getString(R.string.about_summary, Build.VERSION.RELEASE));
+    }
+}
diff --git a/src/com/android/car/settings/system/AboutSettingsFragment.java b/src/com/android/car/settings/system/AboutSettingsFragment.java
index c4fe8d9..4171542 100644
--- a/src/com/android/car/settings/system/AboutSettingsFragment.java
+++ b/src/com/android/car/settings/system/AboutSettingsFragment.java
@@ -16,74 +16,19 @@
 
 package com.android.car.settings.system;
 
-import android.os.Build;
-import android.os.Bundle;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.settingslib.DeviceInfoUtils;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.android.car.settings.common.SettingsFragment;
 
 /**
  * Shows basic info about the system and provide some actions like update, reset etc.
  */
-public class AboutSettingsFragment extends ListItemSettingsFragment {
-
-    private ListItemProvider mItemProvider;
-
-    public static AboutSettingsFragment getInstance() {
-        AboutSettingsFragment aboutSettingsFragment = new AboutSettingsFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.about_settings);
-        aboutSettingsFragment.setArguments(bundle);
-        return aboutSettingsFragment;
-    }
+public class AboutSettingsFragment extends SettingsFragment {
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        mItemProvider = new ListItemProvider.ListProvider(getListItems());
-        // super.onActivityCreated() will need itemProvider, so call it after the provider
-        // is initialized.
-        super.onActivityCreated(savedInstanceState);
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        return mItemProvider;
-    }
-
-    private List<ListItem> getListItems() {
-        List<ListItem> listItems = new ArrayList<>();
-        TextListItem modelItem = new TextListItem(getContext());
-        modelItem.setTitle(getString(R.string.model_info));
-        modelItem.setBody(Build.MODEL + DeviceInfoUtils.getMsvSuffix());
-        listItems.add(modelItem);
-
-        TextListItem androidVersionItem = new TextListItem(getContext());
-        androidVersionItem.setTitle(getString(R.string.firmware_version));
-        androidVersionItem.setBody(getString(R.string.about_summary, Build.VERSION.RELEASE));
-        listItems.add(androidVersionItem);
-
-        TextListItem securityLevelItem = new TextListItem(getContext());
-        securityLevelItem.setTitle(getString(R.string.security_patch));
-        securityLevelItem.setBody(DeviceInfoUtils.getSecurityPatch());
-        listItems.add(securityLevelItem);
-
-        TextListItem kernelVersionItem = new TextListItem(getContext());
-        kernelVersionItem.setTitle(getString(R.string.kernel_version));
-        kernelVersionItem.setBody(DeviceInfoUtils.getFormattedKernelVersion(getContext()));
-        listItems.add(kernelVersionItem);
-
-        TextListItem buildNumberItem = new TextListItem(getContext());
-        buildNumberItem.setTitle(getString(R.string.build_number));
-        buildNumberItem.setBody(Build.DISPLAY);
-        listItems.add(buildNumberItem);
-        return listItems;
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.about_settings_fragment;
     }
 }
diff --git a/src/com/android/car/settings/system/BluetoothMacAddressPreferenceController.java b/src/com/android/car/settings/system/BluetoothMacAddressPreferenceController.java
new file mode 100644
index 0000000..05f9c60
--- /dev/null
+++ b/src/com/android/car/settings/system/BluetoothMacAddressPreferenceController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Updates the vehicle bluetooth mac address summary. */
+public class BluetoothMacAddressPreferenceController extends
+        PreferenceController<Preference> {
+
+    private BluetoothAdapter mBluetoothAdapter;
+
+    public BluetoothMacAddressPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        return super.getAvailabilityStatus();
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(mBluetoothAdapter.getAddress());
+    }
+}
diff --git a/src/com/android/car/settings/system/BuildNumberPreferenceController.java b/src/com/android/car/settings/system/BuildNumberPreferenceController.java
new file mode 100644
index 0000000..c2b7718
--- /dev/null
+++ b/src/com/android/car/settings/system/BuildNumberPreferenceController.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.Build;
+import android.widget.Toast;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.development.DevelopmentSettingsUtil;
+
+/** Updates the build number entry summary with the build number. */
+public class BuildNumberPreferenceController extends PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private Toast mDevHitToast;
+    private int mDevHitCountdown;
+
+    public BuildNumberPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    /**
+     * Reset the toast and counter which tracks how many more clicks until development settings is
+     * enabled.
+     */
+    @Override
+    protected void onResumeInternal() {
+        mDevHitToast = null;
+        mDevHitCountdown = DevelopmentSettingsUtil.isDevelopmentSettingsEnabled(getContext(),
+                mCarUserManagerHelper) ? -1 : getTapsToBecomeDeveloper();
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(Build.DISPLAY);
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        if (!mCarUserManagerHelper.isCurrentProcessAdminUser()
+                && !mCarUserManagerHelper.isCurrentProcessDemoUser()) {
+            return false;
+        }
+
+        if (!DevelopmentSettingsUtil.isDeviceProvisioned(getContext())) {
+            return false;
+        }
+
+        if (mDevHitCountdown > 0) {
+            mDevHitCountdown--;
+            if (mDevHitCountdown == 0) {
+                DevelopmentSettingsUtil.setDevelopmentSettingsEnabled(getContext(), true);
+                showToast(getContext().getString(R.string.show_dev_on), Toast.LENGTH_LONG);
+            } else if (mDevHitCountdown <= getTapsToBecomeDeveloper() - getTapsToShowToast()) {
+                showToast(getContext().getResources().getQuantityString(
+                        R.plurals.show_dev_countdown, mDevHitCountdown, mDevHitCountdown),
+                        Toast.LENGTH_SHORT);
+            }
+        } else if (mDevHitCountdown < 0) {
+            showToast(getContext().getString(R.string.show_dev_already), Toast.LENGTH_LONG);
+        }
+        return true;
+    }
+
+    private void showToast(String text, @Toast.Duration int duration) {
+        if (mDevHitToast != null) {
+            mDevHitToast.cancel();
+        }
+        mDevHitToast = Toast.makeText(getContext(), text, duration);
+        mDevHitToast.show();
+    }
+
+    private int getTapsToBecomeDeveloper() {
+        return getContext().getResources().getInteger(
+                R.integer.enable_developer_settings_click_count);
+    }
+
+    private int getTapsToShowToast() {
+        return getContext().getResources().getInteger(
+                R.integer.enable_developer_settings_clicks_to_show_toast_count);
+    }
+}
diff --git a/src/com/android/car/settings/system/DeveloperOptionsEntryPreferenceController.java b/src/com/android/car/settings/system/DeveloperOptionsEntryPreferenceController.java
new file mode 100644
index 0000000..9b2b22b
--- /dev/null
+++ b/src/com/android/car/settings/system/DeveloperOptionsEntryPreferenceController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.development.DevelopmentSettingsUtil;
+
+/** Controls the visibility of the developer options setting. */
+public class DeveloperOptionsEntryPreferenceController extends PreferenceController<Preference> {
+
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    public DeveloperOptionsEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return DevelopmentSettingsUtil.isDevelopmentSettingsEnabled(getContext(),
+                mCarUserManagerHelper) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+}
diff --git a/src/com/android/car/settings/system/FirmwareVersionPreferenceController.java b/src/com/android/car/settings/system/FirmwareVersionPreferenceController.java
new file mode 100644
index 0000000..0d24dec
--- /dev/null
+++ b/src/com/android/car/settings/system/FirmwareVersionPreferenceController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Updates the firmware version entry summary with the firmware version. */
+public class FirmwareVersionPreferenceController extends PreferenceController<Preference> {
+
+    public FirmwareVersionPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(
+                getContext().getString(R.string.about_summary, Build.VERSION.RELEASE));
+    }
+}
diff --git a/src/com/android/car/settings/system/KernelVersionPreferenceController.java b/src/com/android/car/settings/system/KernelVersionPreferenceController.java
new file mode 100644
index 0000000..1d18523
--- /dev/null
+++ b/src/com/android/car/settings/system/KernelVersionPreferenceController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.DeviceInfoUtils;
+
+/** Updates the kernel version entry summary with the kernel version. */
+public class KernelVersionPreferenceController extends PreferenceController<Preference> {
+
+    public KernelVersionPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(DeviceInfoUtils.getFormattedKernelVersion(getContext()));
+    }
+}
diff --git a/src/com/android/car/settings/system/LanguagePickerFragment.java b/src/com/android/car/settings/system/LanguagePickerFragment.java
deleted file mode 100644
index 2465a18..0000000
--- a/src/com/android/car/settings/system/LanguagePickerFragment.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.system;
-
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.MutableLiveData;
-import android.arch.lifecycle.ViewModel;
-import android.arch.lifecycle.ViewModelProviders;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-
-import androidx.car.widget.ListItemProvider;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.car.settingslib.language.LanguagePickerUtils;
-import com.android.car.settingslib.language.LocaleListItemProvider;
-import com.android.car.settingslib.language.LocaleSelectionListener;
-import com.android.internal.app.LocalePicker;
-import com.android.internal.app.LocaleStore;
-
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Fragment for showing the list of languages.
- */
-public class LanguagePickerFragment extends ListItemSettingsFragment implements
-        LocaleSelectionListener {
-
-    private LocaleListItemProvider mLocaleListItemProvider;
-    private final HashSet<String> mLangTagsToIgnore = new HashSet<>();
-
-    /**
-     * Factory method for creating LanguagePickerFragment.
-     */
-    public static LanguagePickerFragment newInstance() {
-        LanguagePickerFragment LanguagePickerFragment = new LanguagePickerFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.language_settings);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar);
-        LanguagePickerFragment.setArguments(bundle);
-        return LanguagePickerFragment;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        LocaleViewModel viewModel = getLocaleViewModel();
-        viewModel.getLocaleInfos(getContext(), mLangTagsToIgnore).observe(this,
-                localeInfos -> resetLocaleList(localeInfos));
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        if (mLocaleListItemProvider == null) {
-            mLocaleListItemProvider = new LocaleListItemProvider(
-                    getContext(),
-                    new HashSet<LocaleStore.LocaleInfo>(),
-                    /* localeSelectionListener= */ this,
-                    mLangTagsToIgnore);
-        }
-        return mLocaleListItemProvider;
-    }
-
-    @Override
-    public void onLocaleSelected(LocaleStore.LocaleInfo localeInfo) {
-        if (localeInfo == null || localeInfo.getLocale() == null) {
-            return;
-        }
-
-        Locale locale = localeInfo.getLocale();
-
-        Resources res = getResources();
-        Configuration baseConfig = res.getConfiguration();
-        Configuration config = new Configuration(baseConfig);
-        config.setLocale(locale);
-        res.updateConfiguration(config, null);
-
-        // Apply locale to system.
-        LocalePicker.updateLocale(locale);
-        getFragmentController().goBack();
-    }
-
-    @Override
-    public void onParentWithChildrenLocaleSelected(LocaleStore.LocaleInfo localeInfo) {
-        if (localeInfo != null) {
-            setTitle(localeInfo.getFullNameNative());
-            refreshList();
-        }
-    }
-
-    @Override
-    protected void onBackPressed() {
-        if (isChildLocaleDisplayed()) {
-            setTitle(getString(R.string.language_settings));
-            getLocaleViewModel().reloadLocales(getContext(), mLangTagsToIgnore);
-        } else {
-            super.onBackPressed();
-        }
-    }
-
-    private LocaleViewModel getLocaleViewModel() {
-        return ViewModelProviders.of(this).get(LocaleViewModel.class);
-    }
-
-    private boolean isChildLocaleDisplayed() {
-        return mLocaleListItemProvider != null && mLocaleListItemProvider.isChildLocale();
-    }
-
-    /**
-     * Add a pseudo locale in debug build for testing RTL.
-     *
-     * @param localeInfos the set of {@link LocaleStore.LocaleInfo} to which the locale is added.
-     */
-    private void maybeAddPseudoLocale(Set<LocaleStore.LocaleInfo> localeInfos) {
-        if (Build.IS_USERDEBUG) {
-            // The ar-XB pseudo-locale is RTL.
-            localeInfos.add(LocaleStore.getLocaleInfo(new Locale("ar", "XB")));
-        }
-    }
-
-    private void resetLocaleList(Set<LocaleStore.LocaleInfo> localeInfos) {
-        if (mLocaleListItemProvider != null) {
-            maybeAddPseudoLocale(localeInfos);
-            mLocaleListItemProvider.updateSuggestedLocaleAdapter(
-                    LanguagePickerUtils.createSuggestedLocaleAdapter(
-                            getContext(), localeInfos, /* parent= */ null),
-                    /* isChildLocale= */ false);
-            refreshList();
-        }
-    }
-
-    /**
-     * ViewModel for holding the LocaleInfos.
-     */
-    public static class LocaleViewModel extends ViewModel {
-
-        private MutableLiveData<Set<LocaleStore.LocaleInfo>> mLocaleInfos;
-
-        /**
-         * Returns LiveData holding a set of LocaleInfo.
-         */
-        public LiveData<Set<LocaleStore.LocaleInfo>> getLocaleInfos(Context context,
-                Set<String> ignorables) {
-
-            if (mLocaleInfos == null) {
-                mLocaleInfos = new MutableLiveData<Set<LocaleStore.LocaleInfo>>();
-                reloadLocales(context, ignorables);
-            }
-            return mLocaleInfos;
-        }
-
-        /**
-         * Reload the locales based on the current context.
-         */
-        public void reloadLocales(Context context, Set<String> ignorables) {
-            new AsyncTask<Context, Void, Set<LocaleStore.LocaleInfo>>() {
-                @Override
-                protected Set<LocaleStore.LocaleInfo> doInBackground(Context... contexts) {
-                    return LocaleStore.getLevelLocales(
-                            contexts[0],
-                            ignorables,
-                            /* parent= */ null,
-                            /* translatedOnly= */ true);
-                }
-
-                @Override
-                protected void onPostExecute(Set<LocaleStore.LocaleInfo> localeInfos) {
-                    LocaleViewModel.this.mLocaleInfos.setValue(localeInfos);
-                }
-            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, context);
-        }
-    }
-}
diff --git a/src/com/android/car/settings/system/LegalInformationFragment.java b/src/com/android/car/settings/system/LegalInformationFragment.java
new file mode 100644
index 0000000..69b9bc6
--- /dev/null
+++ b/src/com/android/car/settings/system/LegalInformationFragment.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Fragment showing legal information.
+ */
+public class LegalInformationFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.legal_information_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/system/LicenseHtmlGeneratorFromXml.java b/src/com/android/car/settings/system/LicenseHtmlGeneratorFromXml.java
new file mode 100644
index 0000000..8002ac1
--- /dev/null
+++ b/src/com/android/car/settings/system/LicenseHtmlGeneratorFromXml.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.text.TextUtils;
+import android.util.Xml;
+
+import com.android.car.settings.common.Logger;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * The utility class that generate a license html file from xml files.
+ * All the HTML snippets and logic are copied from build/make/tools/generate-notice-files.py.
+ */
+class LicenseHtmlGeneratorFromXml {
+    private static final Logger LOG = new Logger(LicenseHtmlGeneratorFromXml.class);
+
+    private static final String TAG_ROOT = "licenses";
+    private static final String TAG_FILE_NAME = "file-name";
+    private static final String TAG_FILE_CONTENT = "file-content";
+    private static final String ATTR_CONTENT_ID = "contentId";
+    private static final String HTML_HEAD_STRING =
+            "<html><head>\n"
+                    + "<style type=\"text/css\">\n"
+                    + "body { padding: 0; font-family: sans-serif; }\n"
+                    + ".same-license { background-color: #eeeeee;\n"
+                    + "                border-top: 20px solid white;\n"
+                    + "                padding: 10px; }\n"
+                    + ".label { font-weight: bold; }\n"
+                    + ".file-list { margin-left: 1em; color: blue; }\n"
+                    + "</style>\n"
+                    + "</head>"
+                    + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">"
+                    + "\n"
+                    + "<div class=\"toc\">\n"
+                    + "<ul>";
+
+    private static final String HTML_MIDDLE_STRING =
+            "</ul>\n"
+                    + "</div><!-- table of contents -->\n"
+                    + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">";
+
+    private static final String HTML_REAR_STRING =
+            "</table></body></html>";
+
+    private final List<File> mXmlFiles;
+
+    /*
+     * A map from a file name to a content id (MD5 sum of file content) for its license.
+     * For example, "/system/priv-app/TeleService/TeleService.apk" maps to
+     * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum
+     * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2.
+     */
+    private final Map<String, String> mFileNameToContentIdMap = new HashMap();
+
+    /*
+     * A map from a content id (MD5 sum of file content) to a license file content.
+     * For example, "9645f39e9db895a4aa6e02cb57294595" maps to the content string of
+     * packages/services/Telephony/MODULE_LICENSE_APACHE2. Here "9645f39e9db895a4aa6e02cb57294595"
+     * is a MD5 sum of the file content.
+     */
+    private final Map<String, String> mContentIdToFileContentMap = new HashMap();
+
+    static class ContentIdAndFileNames {
+        final String mContentId;
+        final List<String> mFileNameList = new ArrayList();
+
+        ContentIdAndFileNames(String contentId) {
+            mContentId = contentId;
+        }
+    }
+
+    private LicenseHtmlGeneratorFromXml(List<File> xmlFiles) {
+        mXmlFiles = xmlFiles;
+    }
+
+    public static boolean generateHtml(List<File> xmlFiles, File outputFile) {
+        LicenseHtmlGeneratorFromXml genertor = new LicenseHtmlGeneratorFromXml(xmlFiles);
+        return genertor.generateHtml(outputFile);
+    }
+
+    private boolean generateHtml(File outputFile) {
+        for (File xmlFile : mXmlFiles) {
+            parse(xmlFile);
+        }
+
+        if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) {
+            return false;
+        }
+
+        PrintWriter writer = null;
+        try {
+            writer = new PrintWriter(outputFile);
+
+            generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer);
+
+            writer.flush();
+            writer.close();
+            return true;
+        } catch (FileNotFoundException | SecurityException e) {
+            LOG.e("Failed to generate " + outputFile, e);
+
+            if (writer != null) {
+                writer.close();
+            }
+            return false;
+        }
+    }
+
+    private void parse(File xmlFile) {
+        if (xmlFile == null || !xmlFile.exists() || xmlFile.length() == 0) {
+            return;
+        }
+
+        InputStreamReader in = null;
+        try {
+            if (xmlFile.getName().endsWith(".gz")) {
+                in = new InputStreamReader(new GZIPInputStream(new FileInputStream(xmlFile)));
+            } else {
+                in = new FileReader(xmlFile);
+            }
+
+            parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap);
+
+            in.close();
+        } catch (XmlPullParserException | IOException e) {
+            LOG.e("Failed to parse " + xmlFile, e);
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException ie) {
+                    LOG.w("Failed to close " + xmlFile);
+                }
+            }
+        }
+    }
+
+    /*
+     * Parses an input stream and fills a map from a file name to a content id for its license
+     * and a map from a content id to a license file content.
+     *
+     * Following xml format is expected from the input stream.
+     *
+     *     <licenses>
+     *     <file-name contentId="content_id_of_license1">file1</file-name>
+     *     <file-name contentId="content_id_of_license2">file2</file-name>
+     *     ...
+     *     <file-content contentId="content_id_of_license1">license1 file contents</file-content>
+     *     <file-content contentId="content_id_of_license2">license2 file contents</file-content>
+     *     ...
+     *     </licenses>
+     */
+    private static void parse(InputStreamReader in, Map<String, String> outFileNameToContentIdMap,
+            Map<String, String> outContentIdToFileContentMap)
+            throws XmlPullParserException, IOException {
+        Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+        Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(in);
+        parser.nextTag();
+
+        parser.require(XmlPullParser.START_TAG, "", TAG_ROOT);
+
+        int state = parser.getEventType();
+        while (state != XmlPullParser.END_DOCUMENT) {
+            if (state == XmlPullParser.START_TAG) {
+                if (TAG_FILE_NAME.equals(parser.getName())) {
+                    String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID);
+                    if (!TextUtils.isEmpty(contentId)) {
+                        String fileName = readText(parser).trim();
+                        if (!TextUtils.isEmpty(fileName)) {
+                            fileNameToContentIdMap.put(fileName, contentId);
+                        }
+                    }
+                } else if (TAG_FILE_CONTENT.equals(parser.getName())) {
+                    String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID);
+                    if (!TextUtils.isEmpty(contentId)
+                            && !outContentIdToFileContentMap.containsKey(contentId)
+                            && !contentIdToFileContentMap.containsKey(contentId)) {
+                        String fileContent = readText(parser);
+                        if (!TextUtils.isEmpty(fileContent)) {
+                            contentIdToFileContentMap.put(contentId, fileContent);
+                        }
+                    }
+                }
+            }
+
+            state = parser.next();
+        }
+        outFileNameToContentIdMap.putAll(fileNameToContentIdMap);
+        outContentIdToFileContentMap.putAll(contentIdToFileContentMap);
+    }
+
+    private static String readText(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        StringBuffer result = new StringBuffer();
+        int state = parser.next();
+        while (state == XmlPullParser.TEXT) {
+            result.append(parser.getText());
+            state = parser.next();
+        }
+        return result.toString();
+    }
+
+    private static void generateHtml(Map<String, String> fileNameToContentIdMap,
+            Map<String, String> contentIdToFileContentMap, PrintWriter writer) {
+        List<String> fileNameList = new ArrayList();
+        fileNameList.addAll(fileNameToContentIdMap.keySet());
+        Collections.sort(fileNameList);
+
+        writer.println(HTML_HEAD_STRING);
+
+        int count = 0;
+        Map<String, Integer> contentIdToOrderMap = new HashMap();
+        List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList();
+
+        // Prints all the file list with a link to its license file content.
+        for (String fileName : fileNameList) {
+            String contentId = fileNameToContentIdMap.get(fileName);
+            // Assigns an id to a newly referred license file content.
+            if (!contentIdToOrderMap.containsKey(contentId)) {
+                contentIdToOrderMap.put(contentId, count);
+
+                // An index in contentIdAndFileNamesList is the order of each element.
+                contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
+                count++;
+            }
+
+            int id = contentIdToOrderMap.get(contentId);
+            contentIdAndFileNamesList.get(id).mFileNameList.add(fileName);
+            writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+        }
+
+        writer.println(HTML_MIDDLE_STRING);
+
+        count = 0;
+        // Prints all contents of the license files in order of id.
+        for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
+            writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count);
+            writer.println("<div class=\"label\">Notices for file(s):</div>");
+            writer.println("<div class=\"file-list\">");
+            for (String fileName : contentIdAndFileNames.mFileNameList) {
+                writer.format("%s <br/>\n", fileName);
+            }
+            writer.println("</div><!-- file-list -->");
+            writer.println("<pre class=\"license-text\">");
+            writer.println(contentIdToFileContentMap.get(
+                    contentIdAndFileNames.mContentId));
+            writer.println("</pre><!-- license-text -->");
+            writer.println("</td></tr><!-- same-license -->");
+
+            count++;
+        }
+
+        writer.println(HTML_REAR_STRING);
+    }
+}
diff --git a/src/com/android/car/settings/system/LicenseHtmlLoader.java b/src/com/android/car/settings/system/LicenseHtmlLoader.java
new file mode 100644
index 0000000..8bdf9f7
--- /dev/null
+++ b/src/com/android/car/settings/system/LicenseHtmlLoader.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.content.Context;
+
+import com.android.car.settings.common.Logger;
+import com.android.car.settingslib.loader.AsyncLoader;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LicenseHtmlLoader is a loader which loads a license html file from default license xml files.
+ */
+public class LicenseHtmlLoader extends AsyncLoader<File> {
+    private static final Logger LOG = new Logger(LicenseHtmlLoader.class);
+
+    private static final String[] DEFAULT_LICENSE_XML_PATHS = {
+            "/system/etc/NOTICE.xml.gz",
+            "/vendor/etc/NOTICE.xml.gz",
+            "/odm/etc/NOTICE.xml.gz",
+            "/oem/etc/NOTICE.xml.gz"};
+    private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
+
+    private final Context mContext;
+
+    public LicenseHtmlLoader(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public File loadInBackground() {
+        return generateHtmlFromDefaultXmlFiles();
+    }
+
+    private File generateHtmlFromDefaultXmlFiles() {
+        final List<File> xmlFiles = getVaildXmlFiles();
+        if (xmlFiles.isEmpty()) {
+            LOG.e("No notice file exists.");
+            return null;
+        }
+
+        File cachedHtmlFile = getCachedHtmlFile();
+        if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile)
+                || generateHtmlFile(xmlFiles, cachedHtmlFile)) {
+            return cachedHtmlFile;
+        }
+
+        return null;
+    }
+
+    private List<File> getVaildXmlFiles() {
+        final List<File> xmlFiles = new ArrayList();
+        for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) {
+            File file = new File(xmlPath);
+            if (file.exists() && file.length() != 0) {
+                xmlFiles.add(file);
+            }
+        }
+        return xmlFiles;
+    }
+
+    private File getCachedHtmlFile() {
+        return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME);
+    }
+
+    private boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) {
+        boolean outdated = true;
+        if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) {
+            outdated = false;
+            for (File file : xmlFiles) {
+                if (cachedHtmlFile.lastModified() < file.lastModified()) {
+                    outdated = true;
+                    break;
+                }
+            }
+        }
+        return outdated;
+    }
+
+    private boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) {
+        return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile);
+    }
+}
+
+
diff --git a/src/com/android/car/settings/system/MasterClearAccountsPreferenceController.java b/src/com/android/car/settings/system/MasterClearAccountsPreferenceController.java
new file mode 100644
index 0000000..eda3161
--- /dev/null
+++ b/src/com/android/car/settings/system/MasterClearAccountsPreferenceController.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Displays the currently signed in accounts on the vehicle to inform the user that they will be
+ * removed during a factory reset.
+ */
+public class MasterClearAccountsPreferenceController extends PreferenceController<PreferenceGroup> {
+
+    private static final Logger LOG = new Logger(MasterClearAccountsPreferenceController.class);
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final Map<Account, Preference> mAccountPreferenceMap = new ArrayMap<>();
+
+    public MasterClearAccountsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        getPreference().addPreference(
+                createPreference(getContext().getString(R.string.master_clear_accounts), /* icon= */
+                        null));
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        // Refresh the accounts in the off chance an account was added or removed while stopped.
+        Set<Account> accountsToRemove = new HashSet<>(mAccountPreferenceMap.keySet());
+        List<UserInfo> profiles = UserManager.get(getContext()).getProfiles(
+                mCarUserManagerHelper.getCurrentProcessUserId());
+        for (UserInfo profile : profiles) {
+            UserHandle userHandle = new UserHandle(profile.id);
+            AuthenticatorDescription[] descriptions = AccountManager.get(
+                    getContext()).getAuthenticatorTypesAsUser(profile.id);
+            Account[] accounts = AccountManager.get(getContext()).getAccountsAsUser(profile.id);
+            for (Account account : accounts) {
+                AuthenticatorDescription description = null;
+                for (AuthenticatorDescription desc : descriptions) {
+                    if (account.type.equals(desc.type)) {
+                        description = desc;
+                        break;
+                    }
+                }
+                if (description == null) {
+                    LOG.w("No descriptor for account name=" + account.name + " type="
+                            + account.type);
+                    continue;
+                }
+
+                accountsToRemove.remove(account);
+                if (!mAccountPreferenceMap.containsKey(account)) {
+                    Preference accountPref = createPreference(account.name,
+                            getAccountIcon(description, userHandle));
+                    mAccountPreferenceMap.put(account, accountPref);
+                    preferenceGroup.addPreference(accountPref);
+                }
+            }
+        }
+
+        for (Account accountToRemove : accountsToRemove) {
+            preferenceGroup.removePreference(mAccountPreferenceMap.get(accountToRemove));
+        }
+
+        // If the only preference is the title, hide the group.
+        preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 1);
+    }
+
+    private Drawable getAccountIcon(AuthenticatorDescription description, UserHandle userHandle) {
+        Drawable icon = null;
+        try {
+            if (description.iconId != 0) {
+                Context authContext = getContext().createPackageContextAsUser(
+                        description.packageName, /* flags= */ 0, userHandle);
+                icon = getContext().getPackageManager().getUserBadgedIcon(
+                        authContext.getDrawable(description.iconId), userHandle);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            LOG.w("Bad package name for account type " + description.type, e);
+        } catch (Resources.NotFoundException e) {
+            LOG.w("Invalid icon id for account type " + description.type, e);
+        }
+        if (icon == null) {
+            icon = getContext().getPackageManager().getDefaultActivityIcon();
+        }
+        return icon;
+    }
+
+    private Preference createPreference(String title, @Nullable Drawable icon) {
+        Preference preference = new Preference(getContext());
+        preference.setTitle(title);
+        preference.setIcon(icon);
+        preference.setSelectable(false);
+        return preference;
+    }
+}
diff --git a/src/com/android/car/settings/system/MasterClearConfirmFragment.java b/src/com/android/car/settings/system/MasterClearConfirmFragment.java
new file mode 100644
index 0000000..df8b88b
--- /dev/null
+++ b/src/com/android/car/settings/system/MasterClearConfirmFragment.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import android.app.ActivityManager;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.service.oemlock.OemLockManager;
+import android.service.persistentdata.PersistentDataBlockManager;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.preference.PreferenceManager;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Presents the user with a final warning before issuing the request to reset the head unit to its
+ * default "factory" state.
+ */
+public class MasterClearConfirmFragment extends SettingsFragment {
+
+    private Button.OnClickListener mFinalClickListener = v -> {
+        if (ActivityManager.isUserAMonkey()) {
+            return;
+        }
+
+        PersistentDataBlockManager pdbManager =
+                (PersistentDataBlockManager) requireContext().getSystemService(
+                        Context.PERSISTENT_DATA_BLOCK_SERVICE);
+        OemLockManager oemLockManager = (OemLockManager) requireContext().getSystemService(
+                Context.OEM_LOCK_SERVICE);
+        if (pdbManager != null && !oemLockManager.isOemUnlockAllowed()
+                && isDeviceProvisioned()) {
+            // If OEM unlock is allowed, the persistent data block will be wiped during the factory
+            // reset process. If disabled, it will be wiped here, unless the device is still being
+            // provisioned, in which case the persistent data block will be preserved.
+            new AsyncTask<Void, Void, Void>() {
+                private ProgressDialog mProgressDialog;
+
+                @Override
+                protected Void doInBackground(Void... params) {
+                    pdbManager.wipe();
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Void aVoid) {
+                    mProgressDialog.hide();
+                    if (getActivity() != null) {
+                        resetEverything();
+                    }
+                }
+
+                @Override
+                protected void onPreExecute() {
+                    mProgressDialog = getProgressDialog();
+                    mProgressDialog.show();
+                }
+            }.execute();
+        } else {
+            resetEverything();
+        }
+    };
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.master_clear_confirm_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        Button masterClearConfirmButton = requireActivity().findViewById(R.id.action_button1);
+        masterClearConfirmButton.setText(
+                requireContext().getString(R.string.master_clear_confirm_button_text));
+        masterClearConfirmButton.setOnClickListener(mFinalClickListener);
+    }
+
+    private boolean isDeviceProvisioned() {
+        return Settings.Global.getInt(requireContext().getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+    }
+
+    private ProgressDialog getProgressDialog() {
+        ProgressDialog progressDialog = new ProgressDialog(requireContext());
+        progressDialog.setIndeterminate(true);
+        progressDialog.setCancelable(false);
+        progressDialog.setTitle(requireContext().getString(R.string.master_clear_progress_title));
+        progressDialog.setMessage(requireContext().getString(R.string.master_clear_progress_text));
+        return progressDialog;
+    }
+
+    private void resetEverything() {
+        Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
+        intent.setPackage("android");
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm");
+        intent.putExtra(Intent.EXTRA_WIPE_ESIMS, shouldResetEsim());
+        requireActivity().sendBroadcast(intent);
+    }
+
+    private boolean shouldResetEsim() {
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(
+                requireContext());
+        return sharedPreferences.getBoolean(
+                requireContext().getString(R.string.pk_master_clear_reset_esim), false);
+    }
+}
diff --git a/src/com/android/car/settings/system/MasterClearEntryPreferenceController.java b/src/com/android/car/settings/system/MasterClearEntryPreferenceController.java
new file mode 100644
index 0000000..28b8444
--- /dev/null
+++ b/src/com/android/car/settings/system/MasterClearEntryPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static android.os.UserManager.DISALLOW_FACTORY_RESET;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.UserManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller which determines if master clear (aka "factory reset") should be displayed based on
+ * user status.
+ */
+public class MasterClearEntryPreferenceController extends PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public MasterClearEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return isUserRestricted() ? DISABLED_FOR_USER : AVAILABLE;
+    }
+
+    private boolean isUserRestricted() {
+        return !(mCarUserManagerHelper.isCurrentProcessAdminUser() || isDemoUser())
+                || mCarUserManagerHelper.isCurrentProcessUserHasRestriction(DISALLOW_FACTORY_RESET);
+    }
+
+    private boolean isDemoUser() {
+        return UserManager.isDeviceInDemoMode(getContext())
+                && mCarUserManagerHelper.isCurrentProcessDemoUser();
+    }
+}
diff --git a/src/com/android/car/settings/system/MasterClearFragment.java b/src/com/android/car/settings/system/MasterClearFragment.java
new file mode 100644
index 0000000..bba9eae
--- /dev/null
+++ b/src/com/android/car/settings/system/MasterClearFragment.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static android.app.Activity.RESULT_OK;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ActivityResultCallback;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.car.settings.security.CheckLockActivity;
+
+/**
+ * Presents the user with the option to reset the head unit to its default "factory" state. If a
+ * user confirms, the user is first required to authenticate and then presented with a secondary
+ * confirmation: {@link MasterClearConfirmFragment}. The user must scroll to the bottom of the page
+ * before proceeding.
+ */
+public class MasterClearFragment extends SettingsFragment implements ActivityResultCallback {
+
+    // Arbitrary request code for starting CheckLockActivity when the reset button is clicked.
+    @VisibleForTesting
+    static final int CHECK_LOCK_REQUEST_CODE = 88;
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.master_clear_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        Button masterClearButton = requireActivity().findViewById(R.id.action_button1);
+        masterClearButton.setText(requireContext().getString(R.string.master_clear_button_text));
+        masterClearButton.setOnClickListener(
+                v -> startActivityForResult(new Intent(getContext(), CheckLockActivity.class),
+                        CHECK_LOCK_REQUEST_CODE, /* callback= */ this));
+        masterClearButton.setEnabled(false);
+
+        masterClearButton.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        masterClearButton.setEnabled(isAtEnd());
+                        masterClearButton.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                    }
+                });
+        getListView().setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            if (isAtEnd()) {
+                masterClearButton.setEnabled(true);
+            }
+        });
+    }
+
+    @Override
+    public void processActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        if (requestCode == CHECK_LOCK_REQUEST_CODE && resultCode == RESULT_OK) {
+            launchFragment(new MasterClearConfirmFragment());
+        }
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the last item. */
+    private boolean isAtEnd() {
+        RecyclerView.LayoutManager layoutManager = getListView().getLayoutManager();
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return true;
+        }
+
+        int childCount = layoutManager.getChildCount();
+        View lastVisibleChild = layoutManager.getChildAt(childCount - 1);
+
+        // The list has reached the bottom if the last child that is visible is the last item
+        // in the list and it's fully shown.
+        return layoutManager.getPosition(lastVisibleChild) == (layoutManager.getItemCount() - 1)
+                && layoutManager.getDecoratedBottom(lastVisibleChild) <= layoutManager.getHeight();
+    }
+}
diff --git a/src/com/android/car/settings/system/MasterClearOtherUsersPresentPreferenceController.java b/src/com/android/car/settings/system/MasterClearOtherUsersPresentPreferenceController.java
new file mode 100644
index 0000000..86f12cf
--- /dev/null
+++ b/src/com/android/car/settings/system/MasterClearOtherUsersPresentPreferenceController.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Displays a warning message on the factory reset screen when multiple switchable users are present
+ * on the vehicle.
+ */
+public class MasterClearOtherUsersPresentPreferenceController extends
+        PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public MasterClearOtherUsersPresentPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setVisible(!mCarUserManagerHelper.getAllSwitchableUsers().isEmpty());
+    }
+}
diff --git a/src/com/android/car/settings/system/MasterClearResetEsimPreferenceController.java b/src/com/android/car/settings/system/MasterClearResetEsimPreferenceController.java
new file mode 100644
index 0000000..7a695d0
--- /dev/null
+++ b/src/com/android/car/settings/system/MasterClearResetEsimPreferenceController.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.SystemProperties;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Factory reset specific version of {@link ResetEsimPreferenceController} that is only available if
+ * the system property {@code masterclear.allow_retain_esim_profiles_after_fdr} is also true.
+ */
+public class MasterClearResetEsimPreferenceController extends ResetEsimPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_SHOW_ESIM_RESET_CHECKBOX =
+            "masterclear.allow_retain_esim_profiles_after_fdr";
+
+    public MasterClearResetEsimPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        int status = super.getAvailabilityStatus();
+        if (status == AVAILABLE) {
+            return SystemProperties.get(KEY_SHOW_ESIM_RESET_CHECKBOX,
+                    Boolean.FALSE.toString()).equals(Boolean.TRUE.toString()) ? AVAILABLE
+                    : UNSUPPORTED_ON_DEVICE;
+        }
+        return status;
+    }
+}
diff --git a/src/com/android/car/settings/system/ModelInfoPreferenceController.java b/src/com/android/car/settings/system/ModelInfoPreferenceController.java
new file mode 100644
index 0000000..4fd8b53
--- /dev/null
+++ b/src/com/android/car/settings/system/ModelInfoPreferenceController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.DeviceInfoUtils;
+
+/** Updates the model info entry summary with the model info. */
+public class ModelInfoPreferenceController extends PreferenceController<Preference> {
+
+    public ModelInfoPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(Build.MODEL + DeviceInfoUtils.getMsvSuffix());
+    }
+}
diff --git a/src/com/android/car/settings/system/RegulatoryInfoDisplayActivity.java b/src/com/android/car/settings/system/RegulatoryInfoDisplayActivity.java
new file mode 100644
index 0000000..a7deb12
--- /dev/null
+++ b/src/com/android/car/settings/system/RegulatoryInfoDisplayActivity.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.R;
+
+import java.util.Locale;
+
+/**
+ * Code drop from {@link com.android.settings.RegulatoryInfoDisplayActivity}.
+ *
+ * {@link Activity} that displays regulatory information for the "Regulatory information"
+ * preference item, and when "*#07#" is dialed on the Phone keypad. To enable this feature,
+ * set the "config_show_regulatory_info" boolean to true in a device overlay resource, and in the
+ * same overlay, either add a drawable named "regulatory_info.png" containing a graphical version
+ * of the required regulatory info (If ro.bootloader.hardware.sku property is set use
+ * "regulatory_info_<sku>.png where sku is ro.bootloader.hardware.sku property value in lowercase"),
+ * or add a string resource named "regulatory_info_text" with an HTML version of the required
+ * information (text will be centered in the dialog).
+ */
+public class RegulatoryInfoDisplayActivity extends Activity implements
+        DialogInterface.OnDismissListener {
+
+    private static final String DEFAULT_REGULATORY_INFO_FILEPATH =
+            "/data/misc/elabel/regulatory_info.png";
+    private static final String REGULATORY_INFO_FILEPATH_TEMPLATE =
+            "/data/misc/elabel/regulatory_info_%s.png";
+
+    private final String mRegulatoryInfoResource = "regulatory_info";
+
+    /**
+     * Display the regulatory info graphic in a dialog window.
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Resources resources = getResources();
+
+        if (!resources.getBoolean(R.bool.config_show_regulatory_info)) {
+            finish();   // No regulatory info to display for this device.
+        }
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(this)
+                .setTitle(R.string.regulatory_labels)
+                .setOnDismissListener(this);
+
+        boolean regulatoryInfoDrawableExists = false;
+
+        String regulatoryInfoFile = getRegulatoryInfoImageFileName();
+        Bitmap regulatoryInfoBitmap = BitmapFactory.decodeFile(regulatoryInfoFile);
+
+        if (regulatoryInfoBitmap != null) {
+            regulatoryInfoDrawableExists = true;
+        }
+
+        int resId = 0;
+        if (!regulatoryInfoDrawableExists) {
+            resId = getImageResourceId();
+        }
+        if (resId != 0) {
+            try {
+                Drawable d = getDrawable(resId);
+                // Set to false if the width or height is <= 2.
+                // (missing PNG can return an empty 2x2 pixel Drawable)
+                regulatoryInfoDrawableExists = (d.getIntrinsicWidth() > 2
+                        && d.getIntrinsicHeight() > 2);
+            } catch (Resources.NotFoundException ignored) {
+                regulatoryInfoDrawableExists = false;
+            }
+        }
+
+        CharSequence regulatoryText = resources.getText(R.string.regulatory_info_text);
+
+        if (regulatoryInfoDrawableExists) {
+            View view = getLayoutInflater().inflate(R.layout.regulatory_info, null);
+            ImageView image = view.findViewById(R.id.regulatory_info);
+            if (regulatoryInfoBitmap != null) {
+                image.setImageBitmap(regulatoryInfoBitmap);
+            } else {
+                image.setImageResource(resId);
+            }
+            builder.setView(view);
+            builder.show();
+        } else if (regulatoryText.length() > 0) {
+            builder.setMessage(regulatoryText);
+            AlertDialog dialog = builder.show();
+            // We have to show the dialog first, or the setGravity() call will throw a NPE.
+            TextView messageText = (TextView) dialog.findViewById(android.R.id.message);
+            messageText.setGravity(Gravity.CENTER);
+        } else {
+            // Neither drawable nor text resource exists, finish activity.
+            finish();
+        }
+    }
+
+    private int getImageResourceId() {
+        // Use regulatory_info by default.
+        int resId = getResources().getIdentifier(
+                mRegulatoryInfoResource, "drawable", getPackageName());
+
+        // When hardware sku property exists, use regulatory_info_<sku> resource if valid.
+        String sku = getSku();
+        if (!TextUtils.isEmpty(sku)) {
+            String regulatory_info_res = mRegulatoryInfoResource + "_" + sku.toLowerCase();
+            int id = getResources().getIdentifier(
+                    regulatory_info_res, "drawable", getPackageName());
+            if (id != 0) {
+                resId = id;
+            }
+        }
+        return resId;
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        finish();
+    }
+
+    @VisibleForTesting
+    static String getSku() {
+        return SystemProperties.get("ro.boot.hardware.sku", "");
+    }
+
+    @VisibleForTesting
+    static String getRegulatoryInfoImageFileName() {
+        String sku = getSku();
+        if (TextUtils.isEmpty(sku)) {
+            return DEFAULT_REGULATORY_INFO_FILEPATH;
+        } else {
+            return String.format(Locale.US, REGULATORY_INFO_FILEPATH_TEMPLATE, sku.toLowerCase());
+        }
+    }
+}
diff --git a/src/com/android/car/settings/system/ResetAppPrefFragment.java b/src/com/android/car/settings/system/ResetAppPrefFragment.java
new file mode 100644
index 0000000..95f590f
--- /dev/null
+++ b/src/com/android/car/settings/system/ResetAppPrefFragment.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.app.AppOpsManager;
+import android.app.INotificationManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.webkit.IWebViewUpdateService;
+import android.widget.Button;
+import android.widget.Toast;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * Presents the user with information about resetting app preferences.
+ */
+public class ResetAppPrefFragment extends SettingsFragment {
+
+    private static final Logger LOG = new Logger(ResetAppPrefFragment.class);
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.reset_app_pref_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        Button resetAppsButton = requireActivity().findViewById(R.id.action_button1);
+        resetAppsButton.setText(requireContext().getString(R.string.reset_app_pref_button_text));
+        resetAppsButton.setOnClickListener(v -> resetAppPreferences());
+    }
+
+    private void resetAppPreferences() {
+        new ResetTask(requireContext().getApplicationContext()).execute();
+    }
+
+    private static class ResetTask extends AsyncTask<Void, Void, Void> {
+
+        private final WeakReference<Context> mContext;
+
+        ResetTask(Context context) {
+            mContext = new WeakReference<>(context);
+        }
+
+        @Override
+        protected Void doInBackground(Void... unused) {
+            Context context = mContext.get();
+            if (context == null) {
+                LOG.w("Unable to reset app preferences. Null context");
+                return null;
+            }
+            PackageManager packageManager = context.getPackageManager();
+            IBinder notificationManagerServiceBinder = ServiceManager.getService(
+                    Context.NOTIFICATION_SERVICE);
+            if (notificationManagerServiceBinder == null) {
+                LOG.w("Unable to reset app preferences. Null notification manager service");
+                return null;
+            }
+            INotificationManager notificationManagerService =
+                    INotificationManager.Stub.asInterface(notificationManagerServiceBinder);
+            IBinder webViewUpdateServiceBinder = ServiceManager.getService("webviewupdate");
+            if (webViewUpdateServiceBinder == null) {
+                LOG.w("Unable to reset app preferences. Null web view update service");
+                return null;
+            }
+            IWebViewUpdateService webViewUpdateService = IWebViewUpdateService.Stub.asInterface(
+                    webViewUpdateServiceBinder);
+
+            // Reset app notifications.
+            // Reset disabled apps.
+            List<ApplicationInfo> apps = packageManager.getInstalledApplications(
+                    PackageManager.MATCH_DISABLED_COMPONENTS);
+            for (ApplicationInfo app : apps) {
+                try {
+                    notificationManagerService.setNotificationsEnabledForPackage(
+                            app.packageName,
+                            app.uid, true);
+                } catch (RemoteException e) {
+                    LOG.w("Unable to reset notification preferences for app: " + app.name, e);
+                }
+                if (!app.enabled) {
+                    if (packageManager.getApplicationEnabledSetting(app.packageName)
+                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
+                            && !isNonEnableableFallback(webViewUpdateService,
+                            app.packageName)) {
+                        packageManager.setApplicationEnabledSetting(app.packageName,
+                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                                PackageManager.DONT_KILL_APP);
+                    }
+                }
+            }
+
+            // Reset default applications for actions.
+            // Reset background data restrictions for apps.
+            // Reset permission restrictions.
+            try {
+                IBinder packageManagerServiceBinder = ServiceManager.getService("package");
+                if (packageManagerServiceBinder == null) {
+                    LOG.w("Unable to reset app preferences. Null package manager service");
+                    return null;
+                }
+                IPackageManager.Stub.asInterface(
+                        packageManagerServiceBinder).resetApplicationPreferences(
+                        new CarUserManagerHelper(context).getCurrentForegroundUserId());
+            } catch (RemoteException e) {
+                LOG.w("Unable to reset app preferences", e);
+            }
+
+            // Cleanup.
+            ((AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE)).resetAllModes();
+
+            return null;
+        }
+
+        private boolean isNonEnableableFallback(IWebViewUpdateService mWvus, String packageName) {
+            try {
+                return mWvus.isFallbackPackage(packageName);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        protected void onPostExecute(Void unused) {
+            super.onPostExecute(unused);
+            Context context = mContext.get();
+            if (context != null) {
+                Toast.makeText(context, R.string.reset_app_pref_complete_toast,
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
+}
diff --git a/src/com/android/car/settings/system/ResetEsimPreferenceController.java b/src/com/android/car/settings/system/ResetEsimPreferenceController.java
new file mode 100644
index 0000000..928340c
--- /dev/null
+++ b/src/com/android/car/settings/system/ResetEsimPreferenceController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.telephony.euicc.EuiccManager;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller which determines if a checkbox to reset the device's eSIMs is shown. Not all
+ * devices support eSIMs.
+ */
+public class ResetEsimPreferenceController extends PreferenceController<TwoStatePreference> {
+
+    public ResetEsimPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return showEuiccSettings() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    private boolean showEuiccSettings() {
+        EuiccManager euiccManager = (EuiccManager) getContext().getSystemService(
+                Context.EUICC_SERVICE);
+        if (!euiccManager.isEnabled()) {
+            return false;
+        }
+        ContentResolver resolver = getContext().getContentResolver();
+        return Settings.Global.getInt(resolver, Settings.Global.EUICC_PROVISIONED, 0) != 0
+                || Settings.Global.getInt(resolver, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0)
+                != 0;
+    }
+}
diff --git a/src/com/android/car/settings/system/ResetNetworkConfirmFragment.java b/src/com/android/car/settings/system/ResetNetworkConfirmFragment.java
new file mode 100644
index 0000000..11d2240
--- /dev/null
+++ b/src/com/android/car/settings/system/ResetNetworkConfirmFragment.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkPolicyManager;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RecoverySystem;
+import android.provider.Telephony;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.widget.Button;
+import android.widget.Toast;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceManager;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ErrorDialog;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Final warning presented to user to confirm restoring network settings to the factory default.
+ * If a user confirms, all settings are reset for connectivity, Wi-Fi, and Bluetooth.
+ */
+public class ResetNetworkConfirmFragment extends SettingsFragment {
+
+    // Copied from com.android.settings.network.ApnSettings.
+    @VisibleForTesting
+    static final String RESTORE_CARRIERS_URI = "content://telephony/carriers/restore";
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.reset_network_confirm_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        Button resetSettingsButton = requireActivity().findViewById(R.id.action_button1);
+        resetSettingsButton.setText(
+                requireContext().getString(R.string.reset_network_confirm_button_text));
+        resetSettingsButton.setOnClickListener(v -> resetNetwork());
+    }
+
+    private void resetNetwork() {
+        if (ActivityManager.isUserAMonkey()) {
+            return;
+        }
+
+        Context context = requireActivity().getApplicationContext();
+
+        ConnectivityManager connectivityManager = (ConnectivityManager)
+                context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (connectivityManager != null) {
+            connectivityManager.factoryReset();
+        }
+
+        WifiManager wifiManager = (WifiManager)
+                context.getSystemService(Context.WIFI_SERVICE);
+        if (wifiManager != null) {
+            wifiManager.factoryReset();
+        }
+
+        BluetoothManager btManager = (BluetoothManager)
+                context.getSystemService(Context.BLUETOOTH_SERVICE);
+        if (btManager != null) {
+            BluetoothAdapter btAdapter = btManager.getAdapter();
+            if (btAdapter != null) {
+                btAdapter.factoryReset();
+            }
+        }
+
+        int networkSubscriptionId = getNetworkSubscriptionId();
+        TelephonyManager telephonyManager = (TelephonyManager)
+                context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (telephonyManager != null) {
+            telephonyManager.factoryReset(networkSubscriptionId);
+        }
+
+        NetworkPolicyManager policyManager = (NetworkPolicyManager)
+                context.getSystemService(Context.NETWORK_POLICY_SERVICE);
+        if (policyManager != null) {
+            String subscriberId = telephonyManager.getSubscriberId(networkSubscriptionId);
+            policyManager.factoryReset(subscriberId);
+        }
+
+        restoreDefaultApn(context, networkSubscriptionId);
+
+        // There has been issues when Sms raw table somehow stores orphan
+        // fragments. They lead to garbled message when new fragments come
+        // in and combined with those stale ones. In case this happens again,
+        // user can reset all network settings which will clean up this table.
+        cleanUpSmsRawTable(context);
+
+        if (shouldResetEsim()) {
+            new EraseEsimAsyncTask(getContext(), context.getPackageName(), this).execute();
+        } else {
+            showCompletionToast(getContext());
+        }
+    }
+
+    private boolean shouldResetEsim() {
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(
+                requireContext());
+        return sharedPreferences.getBoolean(requireContext().getString(R.string.pk_reset_esim),
+                false);
+    }
+
+    private int getNetworkSubscriptionId() {
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(
+                requireContext());
+        String stringId = sharedPreferences.getString(
+                requireContext().getString(R.string.pk_reset_network_subscription), null);
+        if (TextUtils.isEmpty(stringId)) {
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
+        return Integer.parseInt(stringId);
+    }
+
+    private void restoreDefaultApn(Context context, int subscriptionId) {
+        Uri uri = Uri.parse(RESTORE_CARRIERS_URI);
+
+        if (SubscriptionManager.isUsableSubIdValue(subscriptionId)) {
+            uri = Uri.withAppendedPath(uri, "subId/" + subscriptionId);
+        }
+
+        ContentResolver resolver = context.getContentResolver();
+        resolver.delete(uri, null, null);
+    }
+
+    private void cleanUpSmsRawTable(Context context) {
+        ContentResolver resolver = context.getContentResolver();
+        Uri uri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete");
+        resolver.delete(uri, null, null);
+    }
+
+    private static void showCompletionToast(Context context) {
+        Toast.makeText(context, R.string.reset_network_complete_toast,
+                Toast.LENGTH_SHORT).show();
+    }
+
+    private static class EraseEsimAsyncTask extends AsyncTask<Void, Void, Boolean> {
+
+        private final Context mContext;
+        private final String mPackageName;
+        private final Fragment mFragment;
+
+        EraseEsimAsyncTask(Context context, String packageName, Fragment parent) {
+            mContext = context;
+            mPackageName = packageName;
+            mFragment = parent;
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... voids) {
+            return RecoverySystem.wipeEuiccData(mContext, mPackageName);
+        }
+
+        @Override
+        protected void onPostExecute(Boolean succeeded) {
+            if (succeeded) {
+                showCompletionToast(mContext);
+            } else {
+                ErrorDialog.show(mFragment, R.string.reset_esim_error_title);
+            }
+        }
+    }
+}
diff --git a/src/com/android/car/settings/system/ResetNetworkEntryPreferenceController.java b/src/com/android/car/settings/system/ResetNetworkEntryPreferenceController.java
new file mode 100644
index 0000000..b8ca854
--- /dev/null
+++ b/src/com/android/car/settings/system/ResetNetworkEntryPreferenceController.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static android.os.UserManager.DISALLOW_NETWORK_RESET;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Controller which determines if network reset should be displayed based on user status. */
+public class ResetNetworkEntryPreferenceController extends PreferenceController<Preference> {
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public ResetNetworkEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return isUserRestricted() ? DISABLED_FOR_USER : AVAILABLE;
+    }
+
+    private boolean isUserRestricted() {
+        return !mCarUserManagerHelper.isCurrentProcessAdminUser()
+                || mCarUserManagerHelper.isCurrentProcessUserHasRestriction(DISALLOW_NETWORK_RESET);
+    }
+}
diff --git a/src/com/android/car/settings/system/ResetNetworkFragment.java b/src/com/android/car/settings/system/ResetNetworkFragment.java
new file mode 100644
index 0000000..c4b574f
--- /dev/null
+++ b/src/com/android/car/settings/system/ResetNetworkFragment.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static android.app.Activity.RESULT_OK;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.car.settings.security.CheckLockActivity;
+
+/**
+ * Presents the user with information about restoring network settings to the factory default
+ * values. If a user confirms, they will first be required to authenticate then presented with a
+ * secondary confirmation: {@link ResetNetworkConfirmFragment}.
+ */
+public class ResetNetworkFragment extends SettingsFragment {
+
+    // Arbitrary request code for starting CheckLockActivity when the reset button is clicked.
+    private static final int REQUEST_CODE = 123;
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.reset_network_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        Button resetSettingsButton = requireActivity().findViewById(R.id.action_button1);
+        resetSettingsButton.setText(requireContext().getString(R.string.reset_network_button_text));
+        resetSettingsButton.setOnClickListener(v -> startActivityForResult(new Intent(
+                getContext(), CheckLockActivity.class), REQUEST_CODE));
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
+            launchFragment(new ResetNetworkConfirmFragment());
+        }
+    }
+}
diff --git a/src/com/android/car/settings/system/ResetNetworkItemsPreferenceController.java b/src/com/android/car/settings/system/ResetNetworkItemsPreferenceController.java
new file mode 100644
index 0000000..b24d5f7
--- /dev/null
+++ b/src/com/android/car/settings/system/ResetNetworkItemsPreferenceController.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.style.BulletSpan;
+
+import androidx.annotation.StringRes;
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Controller to determine which items appear as resetable within the reset network description. */
+public class ResetNetworkItemsPreferenceController extends PreferenceController<Preference> {
+
+    public ResetNetworkItemsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        SpannableStringBuilder sb = new SpannableStringBuilder();
+        sb.append(getContext().getString(R.string.reset_network_desc));
+        sb.append(System.lineSeparator());
+        if (hasFeature(PackageManager.FEATURE_WIFI)) {
+            addBulletedValue(sb, R.string.reset_network_item_wifi);
+        }
+        if (hasFeature(PackageManager.FEATURE_TELEPHONY)) {
+            addBulletedValue(sb, R.string.reset_network_item_mobile);
+        }
+        if (hasFeature(PackageManager.FEATURE_BLUETOOTH)) {
+            addBulletedValue(sb, R.string.reset_network_item_bluetooth);
+        }
+        preference.setTitle(sb);
+    }
+
+    private boolean hasFeature(String feature) {
+        return getContext().getPackageManager().hasSystemFeature(feature);
+    }
+
+    private void addBulletedValue(SpannableStringBuilder sb, @StringRes int resId) {
+        sb.append(System.lineSeparator());
+        SpannableString value = new SpannableString(getContext().getString(resId));
+        // Match android.content.res.StringBlock which applies a 10 gapWidth BulletSpan as the <li>
+        // style. This is a workaround for translation specific behavior in StringBlock which led
+        // to multiple indents when building the list using getText(resId).
+        value.setSpan(new BulletSpan(/* gapWidth= */ 10), /* start= */ 0,
+                value.length(), /* flags= */ 0);
+        sb.append(value);
+    }
+}
diff --git a/src/com/android/car/settings/system/ResetNetworkSubscriptionPreferenceController.java b/src/com/android/car/settings/system/ResetNetworkSubscriptionPreferenceController.java
new file mode 100644
index 0000000..fab9b03
--- /dev/null
+++ b/src/com/android/car/settings/system/ResetNetworkSubscriptionPreferenceController.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controller which determines if a network selection option is visible. On devices with multiple
+ * network subscriptions, a user may select the network to reset.
+ */
+public class ResetNetworkSubscriptionPreferenceController extends
+        PreferenceController<ListPreference> {
+
+    private final SubscriptionManager mSubscriptionManager;
+
+    public ResetNetworkSubscriptionPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mSubscriptionManager = (SubscriptionManager) context.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+    }
+
+    @Override
+    protected Class<ListPreference> getPreferenceType() {
+        return ListPreference.class;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    protected void updateState(ListPreference preference) {
+        List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
+        if (subscriptions == null || subscriptions.isEmpty()) {
+            // No subscriptions to reset.
+            preference.setValue(String.valueOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+            preference.setVisible(false);
+            return;
+        }
+        if (subscriptions.size() == 1) {
+            // Only one subscription, so nothing else to select. Use it and hide the preference.
+            preference.setValue(String.valueOf(subscriptions.get(0).getSubscriptionId()));
+            preference.setVisible(false);
+            return;
+        }
+
+        int defaultSubscriptionId = getDefaultSubscriptionId();
+        int selectedIndex = 0;
+        int size = subscriptions.size();
+        List<String> subscriptionNames = new ArrayList<>(size);
+        List<String> subscriptionIds = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            SubscriptionInfo subscription = subscriptions.get(i);
+            int subscriptionId = subscription.getSubscriptionId();
+            if (subscriptionId == defaultSubscriptionId) {
+                // Set the default as the first selected value.
+                selectedIndex = i;
+            }
+            subscriptionNames.add(getSubscriptionName(subscription));
+            subscriptionIds.add(String.valueOf(subscriptionId));
+        }
+
+        preference.setEntries(toCharSequenceArray(subscriptionNames));
+        preference.setEntryValues(toCharSequenceArray(subscriptionIds));
+        preference.setTitle(subscriptionNames.get(selectedIndex));
+        preference.setValueIndex(selectedIndex);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(ListPreference preference, Object newValue) {
+        String subscriptionIdStr = (String) newValue;
+        int index = preference.findIndexOfValue(subscriptionIdStr);
+        CharSequence subscriptionName = preference.getEntries()[index];
+        preference.setTitle(subscriptionName);
+        return true;
+    }
+
+    /**
+     * Returns the default subscription id in the order of data, voice, sms, system subscription.
+     */
+    private int getDefaultSubscriptionId() {
+        int defaultSubscriptionId = SubscriptionManager.getDefaultDataSubscriptionId();
+        if (!SubscriptionManager.isUsableSubIdValue(defaultSubscriptionId)) {
+            defaultSubscriptionId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+        }
+        if (!SubscriptionManager.isUsableSubIdValue(defaultSubscriptionId)) {
+            defaultSubscriptionId = SubscriptionManager.getDefaultSmsSubscriptionId();
+        }
+        if (!SubscriptionManager.isUsableSubIdValue(defaultSubscriptionId)) {
+            defaultSubscriptionId = SubscriptionManager.getDefaultSubscriptionId();
+        }
+        return defaultSubscriptionId;
+    }
+
+    /**
+     * Returns the subscription display name falling back to the number, the carrier, and then
+     * network id codes.
+     */
+    private String getSubscriptionName(SubscriptionInfo subscription) {
+        String name = subscription.getDisplayName().toString();
+        if (TextUtils.isEmpty(name)) {
+            name = subscription.getNumber();
+        }
+        if (TextUtils.isEmpty(name)) {
+            name = subscription.getCarrierName().toString();
+        }
+        if (TextUtils.isEmpty(name)) {
+            name = getContext().getString(R.string.reset_network_fallback_subscription_name,
+                    subscription.getMcc(), subscription.getMnc(), subscription.getSimSlotIndex(),
+                    subscription.getSubscriptionId());
+        }
+        return name;
+    }
+
+    private CharSequence[] toCharSequenceArray(List<String> list) {
+        CharSequence[] array = new CharSequence[list.size()];
+        list.toArray(array);
+        return array;
+    }
+}
diff --git a/src/com/android/car/settings/system/ResetOptionsFragment.java b/src/com/android/car/settings/system/ResetOptionsFragment.java
new file mode 100644
index 0000000..a0643cf
--- /dev/null
+++ b/src/com/android/car/settings/system/ResetOptionsFragment.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Shows options to reset network settings, reset app preferences, and factory reset the device.
+ */
+public class ResetOptionsFragment extends SettingsFragment {
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.reset_options_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/system/SecurityPatchPreferenceController.java b/src/com/android/car/settings/system/SecurityPatchPreferenceController.java
new file mode 100644
index 0000000..f48357c
--- /dev/null
+++ b/src/com/android/car/settings/system/SecurityPatchPreferenceController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.DeviceInfoUtils;
+
+/** Updates the security patch entry summary with the security patch. */
+public class SecurityPatchPreferenceController extends PreferenceController<Preference> {
+
+    public SecurityPatchPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setSummary(DeviceInfoUtils.getSecurityPatch());
+    }
+}
diff --git a/src/com/android/car/settings/system/SystemSettingsFragment.java b/src/com/android/car/settings/system/SystemSettingsFragment.java
index 4f39e32..5b5efc2 100644
--- a/src/com/android/car/settings/system/SystemSettingsFragment.java
+++ b/src/com/android/car/settings/system/SystemSettingsFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -11,135 +11,21 @@
  * 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
+ * limitations under the License.
  */
 
-
 package com.android.car.settings.system;
 
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Bundle;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
-
 import com.android.car.settings.R;
-import com.android.car.settings.common.ExtraSettingsLoader;
-import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.car.settingslib.language.LanguagePickerUtils;
-import com.android.internal.app.LocaleHelper;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Locale;
-import java.util.Map;
+import com.android.car.settings.common.SettingsFragment;
 
 /**
  * Shows basic info about the system and provide some actions like update, reset etc.
  */
-public class SystemSettingsFragment extends ListItemSettingsFragment {
-
-    // Copied from hidden version in android.provider.Settings
-    private static final String ACTION_SYSTEM_UPDATE_SETTINGS =
-            "android.settings.SYSTEM_UPDATE_SETTINGS";
-
-    private static final String ACTION_SETTING_VIEW_LICENSE =
-            "android.settings.WEBVIEW_LICENSE";
-
-    private ListItemProvider mItemProvider;
-
-    public static SystemSettingsFragment getInstance() {
-        SystemSettingsFragment systemSettingsFragment = new SystemSettingsFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.system_setting_title);
-        systemSettingsFragment.setArguments(bundle);
-        return systemSettingsFragment;
-    }
+public class SystemSettingsFragment extends SettingsFragment {
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        mItemProvider = new ListItemProvider.ListProvider(getListItems());
-        // super.onActivityCreated() will need itemProvider, so call it after the provider
-        // is initialized.
-        super.onActivityCreated(savedInstanceState);
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        return mItemProvider;
-    }
-
-    private ArrayList<ListItem> getListItems() {
-        ArrayList<ListItem> lineItems = new ArrayList<>();
-
-        lineItems.add(createLanguageListItem());
-        lineItems.addAll(createSystemUpdateListItems());
-        lineItems.add(createAboutSystemListItem());
-        lineItems.add(createLegalInfoListItem());
-
-        return lineItems;
-    }
-
-    private TextListItem createLanguageListItem() {
-        Context context = getContext();
-        TextListItem languageItem = new TextListItem(context);
-        languageItem.setTitle(context.getString(R.string.language_settings));
-        Locale locale = LanguagePickerUtils.getConfiguredLocale();
-        languageItem.setBody(LocaleHelper.getDisplayName(locale, locale, /* sentenceCase= */ true));
-        languageItem.setPrimaryActionIcon(
-                R.drawable.ic_language, /* useLargeIcon= */ false);
-        languageItem.setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-        languageItem.setOnClickListener(
-                v -> getFragmentController().launchFragment(LanguagePickerFragment.newInstance()));
-        return languageItem;
-    }
-
-    private Collection<ListItem> createSystemUpdateListItems() {
-        Collection<ListItem> collection = new ArrayList<>();
-        Context context = getContext();
-
-        Intent settingsIntent = new Intent(ACTION_SYSTEM_UPDATE_SETTINGS);
-        PackageManager packageManager = context.getPackageManager();
-        if (settingsIntent.resolveActivity(packageManager) != null) {
-            collection.add(new SystemUpdatesListItem(context, settingsIntent));
-        }
-
-        ExtraSettingsLoader extraSettingLoader = new ExtraSettingsLoader(context);
-        Map<String, Collection<ListItem>> extraSettings = extraSettingLoader.load();
-        collection.addAll(extraSettings.get(ExtraSettingsLoader.SYSTEM_CATEGORY));
-        return collection;
-    }
-
-    private TextListItem createAboutSystemListItem() {
-        Context context = getContext();
-        TextListItem aboutSystemItem = new TextListItem(context);
-        aboutSystemItem.setTitle(context.getString(R.string.about_settings));
-        aboutSystemItem.setBody(
-                context.getString(R.string.about_summary, Build.VERSION.RELEASE));
-        aboutSystemItem.setPrimaryActionIcon(
-                R.drawable.ic_settings_about, /* useLargeIcon= */ false);
-        aboutSystemItem.setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-        aboutSystemItem.setOnClickListener(
-                v -> getFragmentController().launchFragment(AboutSettingsFragment.getInstance()));
-        return aboutSystemItem;
-    }
-
-    private TextListItem createLegalInfoListItem() {
-        Context context = getContext();
-        TextListItem legalInfoItem = new TextListItem(context);
-        legalInfoItem.setTitle(context.getString(R.string.legal_information));
-        legalInfoItem.setPrimaryActionIcon(
-                R.drawable.ic_settings_about, /* useLargeIcon= */ false);
-        legalInfoItem.setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
-        legalInfoItem.setOnClickListener(v -> {
-            Intent intent = new Intent();
-            intent.setAction(ACTION_SETTING_VIEW_LICENSE);
-            context.startActivity(intent);
-        });
-        return legalInfoItem;
+    protected int getPreferenceScreenResId() {
+        return R.xml.system_settings_fragment;
     }
 }
diff --git a/src/com/android/car/settings/system/SystemUpdatePreferenceController.java b/src/com/android/car/settings/system/SystemUpdatePreferenceController.java
new file mode 100644
index 0000000..a3389c3
--- /dev/null
+++ b/src/com/android/car/settings/system/SystemUpdatePreferenceController.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static android.content.Context.CARRIER_CONFIG_SERVICE;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.List;
+
+/**
+ * Controller which determines if the system update preference should be displayed based on
+ * device and user status. When the preference is clicked, this controller broadcasts a client
+ * initiated action if an intent is available in carrier-specific telephony configuration.
+ *
+ * @see CarrierConfigManager#KEY_CI_ACTION_ON_SYS_UPDATE_BOOL
+ */
+public class SystemUpdatePreferenceController extends PreferenceController<Preference> {
+
+    private static final Logger LOG = new Logger(SystemUpdatePreferenceController.class);
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private boolean mActivityFound;
+
+    public SystemUpdatePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (!getContext().getResources().getBoolean(R.bool.config_show_system_update_settings)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        return mCarUserManagerHelper.isCurrentProcessAdminUser() ? AVAILABLE : DISABLED_FOR_USER;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        Preference preference = getPreference();
+        Intent intent = preference.getIntent();
+        if (intent != null) {
+            // Find the activity that is in the system image.
+            PackageManager pm = getContext().getPackageManager();
+            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+            int listSize = list.size();
+            for (int i = 0; i < listSize; i++) {
+                ResolveInfo resolveInfo = list.get(i);
+                if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                        != 0) {
+                    // Replace the intent with this specific activity.
+                    preference.setIntent(
+                            new Intent().setClassName(resolveInfo.activityInfo.packageName,
+                                    resolveInfo.activityInfo.name));
+                    // Set the preference title to the activity's label.
+                    preference.setTitle(resolveInfo.loadLabel(pm));
+                    mActivityFound = true;
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setVisible(mActivityFound);
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        CarrierConfigManager configManager = (CarrierConfigManager) getContext().getSystemService(
+                CARRIER_CONFIG_SERVICE);
+        PersistableBundle b = configManager.getConfig();
+        if (b != null && b.getBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)) {
+            ciActionOnSysUpdate(b);
+        }
+        // Don't handle so that preference framework will launch the preference intent.
+        return false;
+    }
+
+    /** Trigger client initiated action (send intent) on system update. */
+    private void ciActionOnSysUpdate(PersistableBundle b) {
+        String intentStr = b.getString(
+                CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING);
+        if (!TextUtils.isEmpty(intentStr)) {
+            String extra = b.getString(
+                    CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING);
+            String extraVal = b.getString(
+                    CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING);
+
+            Intent intent = new Intent(intentStr);
+            if (!TextUtils.isEmpty(extra)) {
+                intent.putExtra(extra, extraVal);
+            }
+            LOG.d("ciActionOnSysUpdate: broadcasting intent " + intentStr + " with extra " + extra
+                    + ", " + extraVal);
+            getContext().getApplicationContext().sendBroadcast(intent);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/system/SystemUpdatesListItem.java b/src/com/android/car/settings/system/SystemUpdatesListItem.java
deleted file mode 100644
index 16e30df..0000000
--- a/src/com/android/car/settings/system/SystemUpdatesListItem.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.settings.system;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.PersistableBundle;
-import android.telephony.CarrierConfigManager;
-import android.text.TextUtils;
-import android.view.View;
-
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-
-
-/**
- * A LineItem that links to system update.
- */
-class SystemUpdatesListItem extends TextListItem {
-    private final Context mContext;
-    private final Intent mSettingsIntent;
-
-    SystemUpdatesListItem(Context context, Intent settingsIntent) {
-        super(context);
-        mContext = context;
-        mSettingsIntent = settingsIntent;
-        setTitle(context.getString(R.string.system_update_settings_list_item_title));
-        setPrimaryActionIcon(R.drawable.ic_system_update, /*useLargeIcon=*/ false);
-        setSupplementalIcon(R.drawable.ic_chevron_right, /*showDivider=*/ false);
-        setOnClickListener(this::onClick);
-    }
-
-    private void onClick(View view) {
-        mContext.startActivity(mSettingsIntent);
-
-        // copy what the phone setting is doing, sending out a carrier defined intent
-        CarrierConfigManager configManager =
-                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle b = configManager.getConfig();
-        if (b == null || !b.getBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)) {
-            return;
-        }
-        String intentStr = b.getString(CarrierConfigManager
-                .KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING);
-        if (!TextUtils.isEmpty(intentStr)) {
-            String extra = b.getString(CarrierConfigManager
-                    .KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING);
-            Intent intent = new Intent(intentStr);
-            if (!TextUtils.isEmpty(extra)) {
-                String extraVal = b.getString(CarrierConfigManager
-                        .KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING);
-                intent.putExtra(extra, extraVal);
-            }
-            mContext.getApplicationContext().sendBroadcast(intent);
-        }
-    }
-}
diff --git a/src/com/android/car/settings/system/ThirdPartyLicensesActivity.java b/src/com/android/car/settings/system/ThirdPartyLicensesActivity.java
new file mode 100644
index 0000000..571e7b7
--- /dev/null
+++ b/src/com/android/car/settings/system/ThirdPartyLicensesActivity.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import androidx.core.content.FileProvider;
+import androidx.fragment.app.FragmentActivity;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+
+import java.io.File;
+
+/**
+ * The activity that displays third-party licenses.
+ */
+public class ThirdPartyLicensesActivity extends FragmentActivity implements
+        LoaderManager.LoaderCallbacks<File> {
+    private static final Logger LOG = new Logger(ThirdPartyLicensesActivity.class);
+    private static final int LOADER_ID_LICENSE_HTML_LOADER = 0;
+    private static final String DEFAULT_LICENSE_PATH = "/system/etc/NOTICE.html.gz";
+    private static final String PROPERTY_LICENSE_PATH = "ro.config.license_path";
+    private static final String FILE_PROVIDER_AUTHORITY = "com.android.settings.files";
+    private static final String HTML_VIEWER_PACKAGE = "com.android.htmlviewer";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final String licenseHtmlPath =
+                SystemProperties.get(PROPERTY_LICENSE_PATH, DEFAULT_LICENSE_PATH);
+        if (isFilePathValid(licenseHtmlPath)) {
+            showSelectedFile(licenseHtmlPath);
+        } else {
+            showHtmlFromDefaultXmlFiles();
+        }
+    }
+
+    @Override
+    public Loader<File> onCreateLoader(int id, Bundle args) {
+        return new LicenseHtmlLoader(this);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<File> loader, File generatedHtmlFile) {
+        showGeneratedHtmlFile(generatedHtmlFile);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<File> loader) {
+    }
+
+    private void showSelectedFile(final String path) {
+        if (TextUtils.isEmpty(path)) {
+            LOG.e("The system property for the license file is empty");
+            showErrorAndFinish();
+            return;
+        }
+
+        final File file = new File(path);
+        if (!isFileValid(file)) {
+            LOG.e("License file " + path + " does not exist");
+            showErrorAndFinish();
+            return;
+        }
+        showHtmlFromUri(Uri.fromFile(file));
+    }
+
+    private void showErrorAndFinish() {
+        Toast.makeText(this, R.string.settings_license_activity_unavailable, Toast.LENGTH_LONG)
+                .show();
+        finish();
+    }
+
+    private void showHtmlFromUri(Uri uri) {
+        // Kick off external viewer due to WebView security restrictions; we
+        // carefully point it at HTMLViewer, since it offers to decompress
+        // before viewing.
+        final Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setDataAndType(uri, "text/html");
+        intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.settings_license_activity_title));
+        if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        }
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.setPackage(HTML_VIEWER_PACKAGE);
+
+        try {
+            startActivity(intent);
+            finish();
+        } catch (ActivityNotFoundException e) {
+            LOG.e("Failed to find viewer", e);
+            showErrorAndFinish();
+        }
+    }
+
+    private void showHtmlFromDefaultXmlFiles() {
+        LoaderManager.getInstance(this).initLoader(LOADER_ID_LICENSE_HTML_LOADER, Bundle.EMPTY,
+                this);
+    }
+
+    private void showGeneratedHtmlFile(File generatedHtmlFile) {
+        if (generatedHtmlFile != null) {
+            LOG.i("File size: " + generatedHtmlFile.length());
+            showHtmlFromUri(getUriFromGeneratedHtmlFile(generatedHtmlFile));
+        } else {
+            LOG.e("Failed to generate.");
+            showErrorAndFinish();
+        }
+    }
+
+    private Uri getUriFromGeneratedHtmlFile(File generatedHtmlFile) {
+        return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, generatedHtmlFile);
+    }
+
+    private boolean isFilePathValid(final String path) {
+        return !TextUtils.isEmpty(path) && isFileValid(new File(path));
+    }
+
+    private boolean isFileValid(final File file) {
+        return file.exists() && file.length() != 0;
+    }
+}
diff --git a/src/com/android/car/settings/system/WifiMacAddressPreferenceController.java b/src/com/android/car/settings/system/WifiMacAddressPreferenceController.java
new file mode 100644
index 0000000..0ef369a
--- /dev/null
+++ b/src/com/android/car/settings/system/WifiMacAddressPreferenceController.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Updates the vehicle Wi-Fi mac address summary. */
+public class WifiMacAddressPreferenceController extends PreferenceController<Preference> {
+
+    private WifiInfo mWifiInfo;
+
+    public WifiMacAddressPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        init(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)
+                || !getContext().getResources().getBoolean(R.bool.config_show_wifi_mac_address)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        return super.getAvailabilityStatus();
+    }
+
+    protected void init(Context context) {
+        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mWifiInfo = wifiManager.getConnectionInfo();
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        String macAddress = mWifiInfo == null ? getContext().getString(R.string.status_unavailable)
+                : mWifiInfo.getMacAddress();
+        preference.setSummary(macAddress);
+    }
+}
diff --git a/src/com/android/car/settings/system/legal/LegalPreferenceController.java b/src/com/android/car/settings/system/legal/LegalPreferenceController.java
new file mode 100644
index 0000000..d05c852
--- /dev/null
+++ b/src/com/android/car/settings/system/legal/LegalPreferenceController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system.legal;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.List;
+
+/**
+ *  Base class for legal preferences. Locates an activity coupled with the given intent and updates
+ *  the preference accordingly.
+ */
+public abstract class LegalPreferenceController extends PreferenceController<Preference> {
+    private final PackageManager mPackageManager;
+    private ResolveInfo mResolveInfo;
+
+    public LegalPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        mResolveInfo = findMatchingSpecificActivity();
+        return mResolveInfo != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        if (mResolveInfo == null) {
+            return;
+        }
+
+        // Replace the intent with this specific activity.
+        preference.setIntent(new Intent().setClassName(
+                mResolveInfo.activityInfo.packageName,
+                mResolveInfo.activityInfo.name));
+
+        preference.setTitle(mResolveInfo.loadLabel(mPackageManager));
+    }
+
+    /** Intent with a matching system activity to display legal disclaimers or licenses. */
+    protected abstract Intent getIntent();
+
+    private ResolveInfo findMatchingSpecificActivity() {
+        Intent intent = getIntent();
+        if (intent == null) {
+            return null;
+        }
+
+        // Find the activity that is in the system image.
+        List<ResolveInfo> list = mPackageManager.queryIntentActivities(intent, /* flags= */ 0);
+        if (list == null) {
+            return null;
+        }
+
+        for (ResolveInfo resolveInfo : list) {
+            if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                    != 0) {
+                return resolveInfo;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/system/legal/TermsPreferenceController.java b/src/com/android/car/settings/system/legal/TermsPreferenceController.java
new file mode 100644
index 0000000..5381a64
--- /dev/null
+++ b/src/com/android/car/settings/system/legal/TermsPreferenceController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system.legal;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Links to a system activity that displays Terms and Conditions. */
+public class TermsPreferenceController extends LegalPreferenceController {
+    private static final Intent INTENT = new Intent("android.settings.TERMS");
+
+    public TermsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Intent getIntent() {
+        return INTENT;
+    }
+}
diff --git a/src/com/android/car/settings/system/legal/ThirdPartyLicensePreferenceController.java b/src/com/android/car/settings/system/legal/ThirdPartyLicensePreferenceController.java
new file mode 100644
index 0000000..c239bc9
--- /dev/null
+++ b/src/com/android/car/settings/system/legal/ThirdPartyLicensePreferenceController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system.legal;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Links to a system activity that displays a list of third party licenses. */
+public class ThirdPartyLicensePreferenceController extends LegalPreferenceController {
+    private static final Intent INTENT = new Intent("android.settings.THIRD_PARTY_LICENSE");
+
+    public ThirdPartyLicensePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Intent getIntent() {
+        return INTENT;
+    }
+}
diff --git a/src/com/android/car/settings/system/legal/WebViewLicensePreferenceController.java b/src/com/android/car/settings/system/legal/WebViewLicensePreferenceController.java
new file mode 100644
index 0000000..e25f7c2
--- /dev/null
+++ b/src/com/android/car/settings/system/legal/WebViewLicensePreferenceController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system.legal;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Links to a system activity that displays System Webview Licenses. */
+public class WebViewLicensePreferenceController extends LegalPreferenceController {
+    private static final Intent INTENT = new Intent("android.settings.WEBVIEW_LICENSE");
+
+    public WebViewLicensePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Intent getIntent() {
+        return INTENT;
+    }
+}
diff --git a/src/com/android/car/settings/tts/PreferredEngineEntryPreferenceController.java b/src/com/android/car/settings/tts/PreferredEngineEntryPreferenceController.java
new file mode 100644
index 0000000..2e1919a
--- /dev/null
+++ b/src/com/android/car/settings/tts/PreferredEngineEntryPreferenceController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+/** Business logic to set the summary for the preferred engine entry setting. */
+public class PreferredEngineEntryPreferenceController extends
+        PreferenceController<ButtonPreference> {
+
+    private static final Logger LOG = new Logger(PreferredEngineEntryPreferenceController.class);
+    private TtsEngines mEnginesHelper;
+
+    public PreferredEngineEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mEnginesHelper = new TtsEngines(context);
+    }
+
+    @Override
+    protected Class<ButtonPreference> getPreferenceType() {
+        return ButtonPreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        getPreference().setOnButtonClickListener(preference -> {
+            TextToSpeech.EngineInfo info = mEnginesHelper.getEngineInfo(
+                    mEnginesHelper.getDefaultEngine());
+            Intent subSettingsIntent = mEnginesHelper.getSettingsIntent(info.name);
+            if (subSettingsIntent != null) {
+                getContext().startActivity(subSettingsIntent);
+            } else {
+                LOG.e("subSettingsIntent is null");
+            }
+        });
+    }
+
+    @Override
+    protected void updateState(ButtonPreference preference) {
+        TextToSpeech.EngineInfo info = mEnginesHelper.getEngineInfo(
+                mEnginesHelper.getDefaultEngine());
+        preference.setSummary(info.label);
+    }
+}
diff --git a/src/com/android/car/settings/tts/PreferredEngineFragment.java b/src/com/android/car/settings/tts/PreferredEngineFragment.java
new file mode 100644
index 0000000..4c71f75
--- /dev/null
+++ b/src/com/android/car/settings/tts/PreferredEngineFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Displays the fragment which gives the user an option to change the preferred TTS engine. */
+public class PreferredEngineFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.preferred_engine_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/tts/PreferredEngineOptionsPreferenceController.java b/src/com/android/car/settings/tts/PreferredEngineOptionsPreferenceController.java
new file mode 100644
index 0000000..e0bbbbc
--- /dev/null
+++ b/src/com/android/car/settings/tts/PreferredEngineOptionsPreferenceController.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+import android.text.TextUtils;
+
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+/** Populates the possible tts engines to set as the preferred engine. */
+public class PreferredEngineOptionsPreferenceController extends
+        PreferenceController<PreferenceGroup> {
+
+    private static final Logger LOG = new Logger(PreferredEngineOptionsPreferenceController.class);
+
+    private final TtsEngines mEnginesHelper;
+    private String mPreviousEngine;
+    private boolean mIsStarted;
+    private TextToSpeech mTts;
+
+    public PreferredEngineOptionsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mEnginesHelper = new TtsEngines(getContext());
+        mIsStarted = false;
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /**
+     * Creates the initial TTS object and constructs the related preferences when underlying
+     * fragment is created.
+     */
+    @Override
+    protected void onCreateInternal() {
+        mTts = new TextToSpeech(getContext(), /* listener= */ null);
+
+        for (TextToSpeech.EngineInfo engine : mEnginesHelper.getEngines()) {
+            TtsPreference preference = new TtsPreference(getContext(), engine);
+            preference.setKey(engine.name);
+            preference.setTitle(engine.label);
+            preference.setOnPreferenceClickListener(pref -> {
+                TextToSpeech.EngineInfo engineInfo = ((TtsPreference) pref).getEngineInfo();
+                TextToSpeech.EngineInfo current = mEnginesHelper.getEngineInfo(
+                        mTts.getCurrentEngine());
+                if (TextUtils.equals(engineInfo.label, current.label)) {
+                    return false;
+                }
+                updateDefaultEngine(engineInfo.name);
+                return true;
+            });
+            getPreference().addPreference(preference);
+        }
+    }
+
+    /** Note that the preference controller was started. */
+    @Override
+    protected void onStartInternal() {
+        mIsStarted = true;
+    }
+
+    /** Note that the preference controller was stopped. */
+    @Override
+    protected void onStopInternal() {
+        mIsStarted = false;
+    }
+
+    /** Cleans up the TTS object and clears the preferences representing the TTS engines. */
+    @Override
+    protected void onDestroyInternal() {
+        if (mTts != null) {
+            mTts.shutdown();
+            mTts = null;
+        }
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        TextToSpeech.EngineInfo current = mEnginesHelper.getEngineInfo(mTts.getCurrentEngine());
+        for (int i = 0; i < preference.getPreferenceCount(); i++) {
+            TtsPreference pref = (TtsPreference) preference.getPreference(i);
+            if (pref.getTitle().equals(current.label)) {
+                pref.setSummary(R.string.text_to_speech_current_engine);
+            } else {
+                pref.setSummary("");
+            }
+        }
+    }
+
+    private void updateDefaultEngine(String engineName) {
+        LOG.d("Updating default synth to : " + engineName);
+
+        // Keep track of the previous engine that was being used. So that
+        // we can reuse the previous engine.
+        //
+        // Note that if TextToSpeech#getCurrentEngine is not null, it means at
+        // the very least that we successfully bound to the engine service.
+        mPreviousEngine = mTts.getCurrentEngine();
+
+        // Step 1: Shut down the existing TTS engine.
+        LOG.i("Shutting down current tts engine");
+        if (mTts != null) {
+            mTts.shutdown();
+        }
+
+        // Step 2: Connect to the new TTS engine.
+        // Step 3 is continued on #onUpdateEngine (below) which is called when
+        // the app binds successfully to the engine.
+        LOG.i("Updating engine : Attempting to connect to engine: " + engineName);
+        mTts = new TextToSpeech(getContext(), status -> {
+            if (mIsStarted) {
+                onUpdateEngine(status);
+                refreshUi();
+            }
+        }, engineName);
+        LOG.i("Success");
+    }
+
+    /**
+     * We have now bound to the TTS engine the user requested. We will attempt to check voice data
+     * for the engine if we successfully bound to it, or revert to the previous engine if we
+     * didn't.
+     */
+    private void onUpdateEngine(int status) {
+        if (status == TextToSpeech.SUCCESS) {
+            LOG.d("Updating engine: Successfully bound to the engine: "
+                    + mTts.getCurrentEngine());
+            Settings.Secure.putString(getContext().getContentResolver(), TTS_DEFAULT_SYNTH,
+                    mTts.getCurrentEngine());
+        } else {
+            LOG.d("Updating engine: Failed to bind to engine, reverting.");
+            if (mPreviousEngine != null) {
+                // This is guaranteed to at least bind, since mPreviousEngine would be
+                // null if the previous bind to this engine failed.
+                mTts = new TextToSpeech(getContext(), /* listener= */ null, mPreviousEngine);
+            }
+            mPreviousEngine = null;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/tts/TextToSpeechOutputFragment.java b/src/com/android/car/settings/tts/TextToSpeechOutputFragment.java
new file mode 100644
index 0000000..bada2bd
--- /dev/null
+++ b/src/com/android/car/settings/tts/TextToSpeechOutputFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Fragment to show the TTS settings. */
+public class TextToSpeechOutputFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.text_to_speech_output_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/tts/TtsPlaybackPreferenceController.java b/src/com/android/car/settings/tts/TtsPlaybackPreferenceController.java
new file mode 100644
index 0000000..00c8ca8
--- /dev/null
+++ b/src/com/android/car/settings/tts/TtsPlaybackPreferenceController.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ActivityResultCallback;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.SeekBarPreference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Business logic for configuring and listening to the current TTS voice. This preference contorller
+ * handles the following:
+ *
+ * <ol>
+ * <li>Changing the TTS language
+ * <li>Changing the TTS speech rate
+ * <li>Changing the TTS voice pitch
+ * <li>Resetting the TTS configuration
+ * </ol>
+ */
+public class TtsPlaybackPreferenceController extends
+        PreferenceController<PreferenceGroup> implements ActivityResultCallback {
+
+    private static final Logger LOG = new Logger(TtsPlaybackPreferenceController.class);
+
+    @VisibleForTesting
+    static final int VOICE_DATA_CHECK = 1;
+    @VisibleForTesting
+    static final int GET_SAMPLE_TEXT = 2;
+
+    private final TtsEngines mEnginesHelper;
+    private TtsPlaybackSettingsManager mTtsPlaybackManager;
+    private TextToSpeech mTts;
+    private int mSelectedLocaleIndex;
+
+    private ListPreference mDefaultLanguagePreference;
+    private SeekBarPreference mSpeechRatePreference;
+    private SeekBarPreference mVoicePitchPreference;
+    private Preference mResetPreference;
+
+    private String mSampleText;
+    private Locale mSampleTextLocale;
+
+    /** True if initialized with no errors. */
+    private boolean mTtsInitialized = false;
+
+    private final TextToSpeech.OnInitListener mOnInitListener = status -> {
+        if (status == TextToSpeech.SUCCESS) {
+            mTtsInitialized = true;
+            refreshUi();
+        }
+    };
+
+    public TtsPlaybackPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mEnginesHelper = new TtsEngines(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mDefaultLanguagePreference = initDefaultLanguagePreference();
+        mSpeechRatePreference = initSpeechRatePreference();
+        mVoicePitchPreference = initVoicePitchPreference();
+        mResetPreference = initResetTtsPlaybackPreference();
+
+        mTts = new TextToSpeech(getContext(), mOnInitListener);
+        mTtsPlaybackManager = new TtsPlaybackSettingsManager(getContext(), mTts, mEnginesHelper);
+        mTts.setSpeechRate(mTtsPlaybackManager.getCurrentSpeechRate()
+                / TtsPlaybackSettingsManager.SCALING_FACTOR);
+        mTts.setPitch(mTtsPlaybackManager.getCurrentVoicePitch()
+                / TtsPlaybackSettingsManager.SCALING_FACTOR);
+        startEngineVoiceDataCheck(mTts.getCurrentEngine());
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        if (mTts != null) {
+            mTts.shutdown();
+            mTts = null;
+            mTtsPlaybackManager = null;
+        }
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        boolean isValid = isDefaultLocaleValid();
+        mDefaultLanguagePreference.setEnabled(isValid);
+        mSpeechRatePreference.setEnabled(isValid);
+        mVoicePitchPreference.setEnabled(isValid);
+        mResetPreference.setEnabled(isValid);
+        if (!isValid && mDefaultLanguagePreference.getEntries() != null) {
+            mDefaultLanguagePreference.setEnabled(true);
+        }
+
+        if (mDefaultLanguagePreference.getEntries() != null) {
+            mDefaultLanguagePreference.setValueIndex(mSelectedLocaleIndex);
+            mDefaultLanguagePreference.setSummary(
+                    mDefaultLanguagePreference.getEntries()[mSelectedLocaleIndex]);
+        }
+
+        mSpeechRatePreference.setValue(mTtsPlaybackManager.getCurrentSpeechRate());
+        mVoicePitchPreference.setValue(mTtsPlaybackManager.getCurrentVoicePitch());
+        checkOrUpdateSampleText();
+    }
+
+    @Override
+    public void processActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        switch (requestCode) {
+            case VOICE_DATA_CHECK:
+                onVoiceDataIntegrityCheckDone(resultCode, data);
+                break;
+            case GET_SAMPLE_TEXT:
+                onSampleTextReceived(resultCode, data);
+                break;
+            default:
+                LOG.e("Got unknown activity result");
+        }
+    }
+
+    private void startEngineVoiceDataCheck(String engine) {
+        Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
+        intent.setPackage(engine);
+        try {
+            LOG.d("Updating engine: Checking voice data: " + intent.toUri(0));
+            getFragmentController().startActivityForResult(intent, VOICE_DATA_CHECK,
+                    this);
+        } catch (ActivityNotFoundException ex) {
+            LOG.e("Failed to check TTS data, no activity found for " + intent);
+        }
+    }
+
+    /**
+     * Ask the current default engine to return a string of sample text to be
+     * spoken to the user.
+     */
+    private void startGetSampleText() {
+        String currentEngine = mTts.getCurrentEngine();
+        if (TextUtils.isEmpty(currentEngine)) {
+            currentEngine = mTts.getDefaultEngine();
+        }
+
+        Intent intent = new Intent(TextToSpeech.Engine.ACTION_GET_SAMPLE_TEXT);
+        mSampleTextLocale = mTtsPlaybackManager.getEffectiveTtsLocale();
+        if (mSampleTextLocale == null) {
+            return;
+        }
+        intent.putExtra(TextToSpeech.Engine.KEY_PARAM_LANGUAGE, mSampleTextLocale.getLanguage());
+        intent.putExtra(TextToSpeech.Engine.KEY_PARAM_COUNTRY, mSampleTextLocale.getCountry());
+        intent.putExtra(TextToSpeech.Engine.KEY_PARAM_VARIANT, mSampleTextLocale.getVariant());
+        intent.setPackage(currentEngine);
+
+        try {
+            LOG.d("Getting sample text: " + intent.toUri(0));
+            getFragmentController().startActivityForResult(intent, GET_SAMPLE_TEXT, this);
+        } catch (ActivityNotFoundException ex) {
+            LOG.e("Failed to get sample text, no activity found for " + intent + ")");
+        }
+    }
+
+    /** The voice data check is complete. */
+    private void onVoiceDataIntegrityCheckDone(int resultCode, Intent data) {
+        String engine = mTts.getCurrentEngine();
+        if (engine == null) {
+            LOG.e("Voice data check complete, but no engine bound");
+            return;
+        }
+
+        if (data == null || resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL) {
+            LOG.e("Engine failed voice data integrity check (null return or invalid result code)"
+                    + mTts.getCurrentEngine());
+            return;
+        }
+
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_SYNTH, engine);
+
+        ArrayList<String> availableLangs =
+                data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
+        if (availableLangs == null || availableLangs.size() == 0) {
+            refreshUi();
+            return;
+        }
+
+        updateDefaultLanguagePreference(availableLangs);
+
+        mSelectedLocaleIndex = findLocaleIndex(mTtsPlaybackManager.getStoredTtsLocale());
+        if (mSelectedLocaleIndex < 0) {
+            mSelectedLocaleIndex = 0;
+        }
+        startGetSampleText();
+        refreshUi();
+    }
+
+    private void onSampleTextReceived(int resultCode, Intent data) {
+        String sample = getContext().getString(R.string.tts_default_sample_string);
+
+        if (resultCode == TextToSpeech.LANG_AVAILABLE && data != null) {
+            String tmp = data.getStringExtra(TextToSpeech.Engine.EXTRA_SAMPLE_TEXT);
+            if (!TextUtils.isEmpty(tmp)) {
+                sample = tmp;
+            }
+            LOG.d("Got sample text: " + sample);
+        } else {
+            LOG.d("Using default sample text :" + sample);
+        }
+
+        mSampleText = sample;
+    }
+
+    private void updateLanguageTo(Locale locale) {
+        int selectedLocaleIndex = findLocaleIndex(locale);
+        if (selectedLocaleIndex == -1) {
+            LOG.w("updateLanguageTo called with unknown locale argument");
+            return;
+        }
+
+        if (mTtsPlaybackManager.updateTtsLocale(locale)) {
+            mSelectedLocaleIndex = selectedLocaleIndex;
+            refreshUi();
+        } else {
+            LOG.e("updateLanguageTo failed to update tts language");
+        }
+    }
+
+    private int findLocaleIndex(Locale locale) {
+        String localeString = (locale != null) ? locale.toString() : "";
+        return mDefaultLanguagePreference.findIndexOfValue(localeString);
+    }
+
+    private boolean isDefaultLocaleValid() {
+        if (!mTtsInitialized) {
+            return false;
+        }
+
+        Locale defaultLocale = mTtsPlaybackManager.getEffectiveTtsLocale();
+        if (defaultLocale == null) {
+            LOG.e("Failed to get default language from engine " + mTts.getCurrentEngine());
+            return false;
+        }
+
+        if (mDefaultLanguagePreference.getEntries() == null) {
+            return false;
+        }
+
+        int index = mDefaultLanguagePreference.findIndexOfValue(defaultLocale.toString());
+        if (index < 0) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void checkOrUpdateSampleText() {
+        if (!mTtsInitialized) {
+            return;
+        }
+        Locale defaultLocale = mTtsPlaybackManager.getEffectiveTtsLocale();
+        if (defaultLocale == null) {
+            LOG.e("Failed to get default language from engine " + mTts.getCurrentEngine());
+            return;
+        }
+
+        if (!Objects.equals(defaultLocale, mSampleTextLocale)) {
+            mSampleText = null;
+            mSampleTextLocale = null;
+        }
+
+        if (mSampleText == null) {
+            startGetSampleText();
+        }
+    }
+
+    @VisibleForTesting
+    String getSampleText() {
+        return mSampleText;
+    }
+
+    /* ***************************************************************************************** *
+     * Preference initialization/update code.                                                    *
+     * ***************************************************************************************** */
+
+    private ListPreference initDefaultLanguagePreference() {
+        ListPreference defaultLanguagePreference = (ListPreference) getPreference().findPreference(
+                getContext().getString(R.string.pk_tts_default_language));
+        defaultLanguagePreference.setOnPreferenceChangeListener((preference, newValue) -> {
+            String localeString = (String) newValue;
+            updateLanguageTo(!TextUtils.isEmpty(localeString) ? mEnginesHelper.parseLocaleString(
+                    localeString) : null);
+            checkOrUpdateSampleText();
+            return true;
+        });
+        return defaultLanguagePreference;
+    }
+
+    private void updateDefaultLanguagePreference(@NonNull ArrayList<String> availableLangs) {
+        // Sort locales by display name.
+        ArrayList<Locale> locales = new ArrayList<>();
+        for (int i = 0; i < availableLangs.size(); i++) {
+            Locale locale = mEnginesHelper.parseLocaleString(availableLangs.get(i));
+            if (locale != null) {
+                locales.add(locale);
+            }
+        }
+        Collections.sort(locales,
+                (lhs, rhs) -> lhs.getDisplayName().compareToIgnoreCase(rhs.getDisplayName()));
+
+        // Separate pairs into two separate arrays.
+        CharSequence[] entries = new CharSequence[availableLangs.size() + 1];
+        CharSequence[] entryValues = new CharSequence[availableLangs.size() + 1];
+
+        entries[0] = getContext().getString(R.string.tts_lang_use_system);
+        entryValues[0] = "";
+
+        int i = 1;
+        for (Locale locale : locales) {
+            entries[i] = locale.getDisplayName();
+            entryValues[i++] = locale.toString();
+        }
+
+        mDefaultLanguagePreference.setEntries(entries);
+        mDefaultLanguagePreference.setEntryValues(entryValues);
+    }
+
+    private SeekBarPreference initSpeechRatePreference() {
+        SeekBarPreference speechRatePreference = (SeekBarPreference) getPreference().findPreference(
+                getContext().getString(R.string.pk_tts_speech_rate));
+        speechRatePreference.setMin(TtsPlaybackSettingsManager.MIN_SPEECH_RATE);
+        speechRatePreference.setMax(TtsPlaybackSettingsManager.MAX_SPEECH_RATE);
+        speechRatePreference.setShowSeekBarValue(false);
+        speechRatePreference.setContinuousUpdate(false);
+        speechRatePreference.setOnPreferenceChangeListener((preference, newValue) -> {
+            if (mTtsPlaybackManager != null) {
+                mTtsPlaybackManager.updateSpeechRate((Integer) newValue);
+                mTtsPlaybackManager.speakSampleText(mSampleText);
+                return true;
+            }
+            LOG.e("speech rate preference enabled before it is allowed");
+            return false;
+        });
+
+        // Initially disable.
+        speechRatePreference.setEnabled(false);
+        return speechRatePreference;
+    }
+
+    private SeekBarPreference initVoicePitchPreference() {
+        SeekBarPreference pitchPreference = (SeekBarPreference) getPreference().findPreference(
+                getContext().getString(R.string.pk_tts_pitch));
+        pitchPreference.setMin(TtsPlaybackSettingsManager.MIN_VOICE_PITCH);
+        pitchPreference.setMax(TtsPlaybackSettingsManager.MAX_VOICE_PITCH);
+        pitchPreference.setShowSeekBarValue(false);
+        pitchPreference.setContinuousUpdate(false);
+        pitchPreference.setOnPreferenceChangeListener((preference, newValue) -> {
+            if (mTtsPlaybackManager != null) {
+                mTtsPlaybackManager.updateVoicePitch((Integer) newValue);
+                mTtsPlaybackManager.speakSampleText(mSampleText);
+                return true;
+            }
+            LOG.e("speech pitch preference enabled before it is allowed");
+            return false;
+        });
+
+        // Initially disable.
+        pitchPreference.setEnabled(false);
+        return pitchPreference;
+    }
+
+    private Preference initResetTtsPlaybackPreference() {
+        Preference resetPreference = getPreference().findPreference(
+                getContext().getString(R.string.pk_tts_reset));
+        resetPreference.setOnPreferenceClickListener(preference -> {
+            if (mTtsPlaybackManager != null) {
+                mTtsPlaybackManager.resetVoicePitch();
+                mTtsPlaybackManager.resetSpeechRate();
+                refreshUi();
+                return true;
+            }
+            LOG.e("reset preference enabled before it is allowed");
+            return false;
+        });
+
+        // Initially disable.
+        resetPreference.setEnabled(false);
+        return resetPreference;
+    }
+}
diff --git a/src/com/android/car/settings/tts/TtsPlaybackSettingsManager.java b/src/com/android/car/settings/tts/TtsPlaybackSettingsManager.java
new file mode 100644
index 0000000..01021bb
--- /dev/null
+++ b/src/com/android/car/settings/tts/TtsPlaybackSettingsManager.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+
+import java.util.Locale;
+
+/** Handles interactions with TTS playback settings. */
+class TtsPlaybackSettingsManager {
+
+    private static final Logger LOG = new Logger(TtsPlaybackSettingsManager.class);
+
+    /**
+     * Maximum speech rate value.
+     */
+    public static final int MAX_SPEECH_RATE = 600;
+
+    /**
+     * Minimum speech rate value.
+     */
+    public static final int MIN_SPEECH_RATE = 10;
+
+    /**
+     * Maximum voice pitch value.
+     */
+    public static final int MAX_VOICE_PITCH = 400;
+
+    /**
+     * Minimum voice pitch value.
+     */
+    public static final int MIN_VOICE_PITCH = 25;
+
+    /**
+     * Scaling factor used to convert speech rate and pitch values between {@link Settings.Secure}
+     * and {@link TextToSpeech}.
+     */
+    public static final float SCALING_FACTOR = 100.0f;
+    private static final String UTTERANCE_ID = "Sample";
+
+    private final Context mContext;
+    private final TextToSpeech mTts;
+    private final TtsEngines mEnginesHelper;
+
+    TtsPlaybackSettingsManager(Context context, @NonNull TextToSpeech tts,
+            @NonNull TtsEngines enginesHelper) {
+        mContext = context;
+        mTts = tts;
+        mEnginesHelper = enginesHelper;
+    }
+
+    void updateSpeechRate(int speechRate) {
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_RATE, speechRate);
+        mTts.setSpeechRate(speechRate / SCALING_FACTOR);
+        LOG.d("TTS default rate changed, now " + speechRate);
+    }
+
+    int getCurrentSpeechRate() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
+    }
+
+    void resetSpeechRate() {
+        updateSpeechRate(TextToSpeech.Engine.DEFAULT_RATE);
+    }
+
+    void updateVoicePitch(int pitch) {
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_PITCH,
+                pitch);
+        mTts.setPitch(pitch / SCALING_FACTOR);
+        LOG.d("TTS default pitch changed, now " + pitch);
+    }
+
+    int getCurrentVoicePitch() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
+    }
+
+    void resetVoicePitch() {
+        updateVoicePitch(TextToSpeech.Engine.DEFAULT_PITCH);
+    }
+
+    /**
+     * Returns the currently stored locale for the given tts engine. It can return {@code null}, if
+     * it is configured to use the system default locale.
+     */
+    @Nullable
+    Locale getStoredTtsLocale() {
+        Locale currentLocale = null;
+        if (!mEnginesHelper.isLocaleSetToDefaultForEngine(mTts.getCurrentEngine())) {
+            currentLocale = mEnginesHelper.getLocalePrefForEngine(mTts.getCurrentEngine());
+        }
+        return currentLocale;
+    }
+
+    /**
+     * Similar to {@link #getStoredTtsLocale()}, but returns the language of the voice registered
+     * to the actual TTS object. It is possible for the TTS voice to be {@code null} if TTS is not
+     * yet initialized.
+     */
+    @Nullable
+    Locale getEffectiveTtsLocale() {
+        if (mTts.getVoice() == null) {
+            return null;
+        }
+        return mEnginesHelper.parseLocaleString(mTts.getVoice().getLocale().toString());
+    }
+
+    /**
+     * Attempts to update the default tts locale. Returns {@code true} if successful, false
+     * otherwise.
+     */
+    boolean updateTtsLocale(Locale newLocale) {
+        int resultCode = mTts.setLanguage((newLocale != null) ? newLocale : Locale.getDefault());
+        boolean success = resultCode != TextToSpeech.LANG_NOT_SUPPORTED
+                && resultCode != TextToSpeech.LANG_MISSING_DATA;
+        if (success) {
+            mEnginesHelper.updateLocalePrefForEngine(mTts.getCurrentEngine(), newLocale);
+        }
+
+        return success;
+    }
+
+    void speakSampleText(String text) {
+        boolean networkRequired = mTts.getVoice().isNetworkConnectionRequired();
+        Locale defaultLocale = getEffectiveTtsLocale();
+        if (!networkRequired || networkRequired && mTts.isLanguageAvailable(defaultLocale)
+                >= TextToSpeech.LANG_AVAILABLE) {
+            mTts.speak(text, TextToSpeech.QUEUE_FLUSH, /* params= */ null, UTTERANCE_ID);
+        } else {
+            displayNetworkAlert();
+        }
+    }
+
+    private void displayNetworkAlert() {
+        AlertDialog dialog = new AlertDialog.Builder(mContext)
+                .setTitle(android.R.string.dialog_alert_title)
+                .setMessage(R.string.tts_engine_network_required)
+                .setCancelable(false)
+                .setPositiveButton(android.R.string.ok, null).create();
+        dialog.show();
+    }
+}
diff --git a/src/com/android/car/settings/tts/TtsPreference.java b/src/com/android/car/settings/tts/TtsPreference.java
new file mode 100644
index 0000000..7c2f8db
--- /dev/null
+++ b/src/com/android/car/settings/tts/TtsPreference.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import android.content.Context;
+import android.speech.tts.TextToSpeech;
+
+import androidx.preference.Preference;
+
+/** Preference which also encapsulates the associated Text-to-speech engine. */
+public class TtsPreference extends Preference {
+
+    private TextToSpeech.EngineInfo mEngineInfo;
+
+    public TtsPreference(Context context, TextToSpeech.EngineInfo engineInfo) {
+        super(context);
+        mEngineInfo = engineInfo;
+    }
+
+    /** Gets the engine info associated with this preference. */
+    public TextToSpeech.EngineInfo getEngineInfo() {
+        return mEngineInfo;
+    }
+}
diff --git a/src/com/android/car/settings/users/AddNewUserTask.java b/src/com/android/car/settings/users/AddNewUserTask.java
index 29f9384..281542d 100644
--- a/src/com/android/car/settings/users/AddNewUserTask.java
+++ b/src/com/android/car/settings/users/AddNewUserTask.java
@@ -16,7 +16,7 @@
 
 package com.android.car.settings.users;
 
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
 import android.content.pm.UserInfo;
 import android.os.AsyncTask;
 
@@ -34,21 +34,19 @@
 
     @Override
     protected UserInfo doInBackground(String... userNames) {
-        // Default to create a non admin user for now. Need to add logic
-        // for user to choose whether they want to create an admin or non-admin
-        // user later.
         return mCarUserManagerHelper.createNewNonAdminUser(userNames[0]);
     }
 
     @Override
-    protected void onPreExecute() {
-    }
+    protected void onPreExecute() { }
 
     @Override
     protected void onPostExecute(UserInfo user) {
-        mAddNewUserListener.onUserAdded();
         if (user != null) {
+            mAddNewUserListener.onUserAddedSuccess();
             mCarUserManagerHelper.switchToUser(user);
+        } else {
+            mAddNewUserListener.onUserAddedFailure();
         }
     }
 
@@ -57,8 +55,13 @@
      */
     public interface AddNewUserListener {
         /**
-         * Invoked in AddNewUserTask.onPostExecute after the task has been completed.
+         * Invoked in AddNewUserTask.onPostExecute after the user has been created successfully.
          */
-        void onUserAdded();
+        void onUserAddedSuccess();
+
+        /**
+         * Invoked in AddNewUserTask.onPostExecute if new user creation failed.
+         */
+        void onUserAddedFailure();
     }
 }
diff --git a/src/com/android/car/settings/users/ChooseNewAdminFragment.java b/src/com/android/car/settings/users/ChooseNewAdminFragment.java
new file mode 100644
index 0000000..09ff61e
--- /dev/null
+++ b/src/com/android/car/settings/users/ChooseNewAdminFragment.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * This screen appears after the last admin on the device tries to delete themselves. (but only if
+ * there are other users on the device)
+ *
+ * <p> It lets the Admin see a list of non-Admins on the device and choose a user from the list to
+ * upgrade to Admin.
+ *
+ * <p> After new admin has been selected and upgraded, the old Admin is removed.
+ */
+public class ChooseNewAdminFragment extends SettingsFragment {
+
+    /**
+     * Creates a new instance of {@link ChooseNewAdminFragment} that enables the last remaining
+     * admin to choose a new Admin from a list of Non-Admins.
+     *
+     * @param adminInfo Admin that will get removed after new admin has been designated.
+     */
+    public static ChooseNewAdminFragment newInstance(UserInfo adminInfo) {
+        ChooseNewAdminFragment usersListFragment = new ChooseNewAdminFragment();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(Intent.EXTRA_USER, adminInfo);
+        usersListFragment.setArguments(bundle);
+        return usersListFragment;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.choose_new_admin_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        UserInfo adminInfo = requireNonNull(getArguments()).getParcelable(
+                Intent.EXTRA_USER);
+        use(ChooseNewAdminPreferenceController.class, R.string.pk_choose_new_admin).setAdminInfo(
+                adminInfo);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        Button cancelBtn = getActivity().findViewById(R.id.action_button1);
+        cancelBtn.setVisibility(View.VISIBLE);
+        cancelBtn.setText(R.string.cancel);
+        cancelBtn.setOnClickListener(v -> getActivity().onBackPressed());
+    }
+}
diff --git a/src/com/android/car/settings/users/ChooseNewAdminPreferenceController.java b/src/com/android/car/settings/users/ChooseNewAdminPreferenceController.java
new file mode 100644
index 0000000..975f5a4
--- /dev/null
+++ b/src/com/android/car/settings/users/ChooseNewAdminPreferenceController.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.ErrorDialog;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Business logic for when the last admin is about to be removed from the device and a new
+ * administrator needs to be chosen.
+ */
+public class ChooseNewAdminPreferenceController extends UsersBasePreferenceController {
+
+    private final ConfirmationDialogFragment.ConfirmListener mConfirmListener = arguments -> {
+        UserInfo userToMakeAdmin = (UserInfo) arguments.get(
+                UsersDialogProvider.KEY_USER_TO_MAKE_ADMIN);
+        assignNewAdminAndRemoveOldAdmin(userToMakeAdmin);
+        getFragmentController().goBack();
+    };
+
+    private UserInfo mAdminInfo;
+
+    public ChooseNewAdminPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        getPreferenceProvider().setIncludeCurrentUser(false);
+        getPreferenceProvider().setIncludeGuest(false);
+    }
+
+    /** Setter for the user info of the admin we're deleting. */
+    public void setAdminInfo(UserInfo adminInfo) {
+        mAdminInfo = adminInfo;
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mAdminInfo == null) {
+            throw new IllegalStateException("Admin info should be set by this point");
+        }
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        super.onCreateInternal();
+        ConfirmationDialogFragment dialogFragment =
+                (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
+                        ConfirmationDialogFragment.TAG);
+
+        ConfirmationDialogFragment.resetListeners(dialogFragment,
+                mConfirmListener, /* rejectListener= */ null);
+    }
+
+    @Override
+    protected void userClicked(UserInfo userToMakeAdmin) {
+
+        ConfirmationDialogFragment dialogFragment =
+                UsersDialogProvider.getConfirmGrantAdminDialogFragment(getContext(),
+                        mConfirmListener, /* rejectListener= */ null, userToMakeAdmin);
+
+        getFragmentController().showDialog(dialogFragment, ConfirmationDialogFragment.TAG);
+    }
+
+    @VisibleForTesting
+    void assignNewAdminAndRemoveOldAdmin(UserInfo userToMakeAdmin) {
+        getCarUserManagerHelper().grantAdminPermissions(userToMakeAdmin);
+        removeOldAdmin();
+    }
+
+    private void removeOldAdmin() {
+        if (!getCarUserManagerHelper().removeUser(mAdminInfo,
+                getContext().getString(R.string.user_guest))) {
+            // If failed, need to show error dialog for users.
+            getFragmentController().showDialog(
+                    ErrorDialog.newInstance(R.string.delete_user_error_title), /* tag= */ null);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/users/ConfirmCreateNewUserDialog.java b/src/com/android/car/settings/users/ConfirmCreateNewUserDialog.java
deleted file mode 100644
index 09b24dc..0000000
--- a/src/com/android/car/settings/users/ConfirmCreateNewUserDialog.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.users;
-
-import static android.content.DialogInterface.BUTTON_NEGATIVE;
-import static android.content.DialogInterface.BUTTON_POSITIVE;
-
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.Fragment;
-
-import androidx.car.app.CarAlertDialog;
-
-import com.android.car.settings.R;
-
-/**
- * Dialog to confirm creation of new user.
- */
-public class ConfirmCreateNewUserDialog extends DialogFragment implements
-        DialogInterface.OnClickListener {
-    private static final String DIALOG_TAG = "ConfirmCreateNewUserDialog";
-    private ConfirmCreateNewUserListener mCreateListener;
-    private CancelCreateNewUserListener mCancelListener;
-
-    /**
-     * Interface for listeners that want to receive a callback when user confirms new user creation
-     * in the dialog.
-     */
-    public interface ConfirmCreateNewUserListener {
-        void onCreateNewUserConfirmed();
-    }
-
-    /**
-     * Interface for listeners that want to receive a callback when user cancels new user creation
-     * in the dialog.
-     */
-    public interface CancelCreateNewUserListener {
-        void onCreateNewUserCancelled();
-    }
-
-    /**
-     * Shows the dialog.
-     *
-     * @param parent Fragment associated with the dialog.
-     */
-    public void show(Fragment parent) {
-        setTargetFragment(parent, 0);
-        show(parent.getFragmentManager(), DIALOG_TAG);
-    }
-
-    /**
-     * Sets a listener for OnCreateNewUserConfirmed that will get called if user confirms
-     * the dialog.
-     *
-     * @param listener Instance of {@link ConfirmCreateNewUserListener} to call when confirmed.
-     */
-    public void setConfirmCreateNewUserListener(ConfirmCreateNewUserListener listener) {
-        mCreateListener = listener;
-    }
-
-    /**
-     * Sets a listener for OnCancelNewUserConfirmed that will get called if user cancels
-     * the dialog.
-     *
-     * @param listener Instance of {@link CancelCreateNewUserListener} to call when user presses
-     * cancel.
-     */
-    public void setCancelCreateNewUserListener(CancelCreateNewUserListener listener) {
-        mCancelListener = listener;
-    }
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        String message = getString(R.string.user_add_user_message_setup)
-                .concat(System.getProperty("line.separator"))
-                .concat(System.getProperty("line.separator"))
-                .concat(getString(R.string.user_add_user_message_update));
-
-        return new CarAlertDialog.Builder(getContext())
-                .setTitle(R.string.user_add_user_title)
-                .setBody(message)
-                .setNegativeButton(android.R.string.cancel, this)
-                .setPositiveButton(android.R.string.ok, this)
-                .create();
-    }
-
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        if (which == BUTTON_POSITIVE) {
-            if (mCreateListener != null) {
-                mCreateListener.onCreateNewUserConfirmed();
-            }
-        } else if (which == BUTTON_NEGATIVE) {
-            if (mCancelListener != null) {
-                mCancelListener.onCreateNewUserCancelled();
-            }
-        }
-
-        dialog.dismiss();
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/car/settings/users/ConfirmExitRetailModeDialog.java b/src/com/android/car/settings/users/ConfirmExitRetailModeDialog.java
index 5b65c4e..6a85227 100644
--- a/src/com/android/car/settings/users/ConfirmExitRetailModeDialog.java
+++ b/src/com/android/car/settings/users/ConfirmExitRetailModeDialog.java
@@ -16,13 +16,14 @@
 
 package com.android.car.settings.users;
 
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.DialogInterface;
 import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.Fragment;
 
-import androidx.car.app.CarAlertDialog;
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
 
 import com.android.car.settings.R;
 
@@ -32,7 +33,8 @@
  */
 public class ConfirmExitRetailModeDialog extends DialogFragment implements
         DialogInterface.OnClickListener {
-    private static final String DIALOG_TAG = "ConfirmExitRetailModeDialog";
+    @VisibleForTesting
+    static final String DIALOG_TAG = "ConfirmExitRetailModeDialog";
     private ConfirmExitRetailModeListener mListener;
 
     /**
@@ -57,9 +59,9 @@
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        return new CarAlertDialog.Builder(getContext())
+        return new AlertDialog.Builder(getContext())
                 .setTitle(R.string.exit_retail_mode_dialog_title)
-                .setBody(R.string.exit_retail_mode_dialog_body)
+                .setMessage(R.string.exit_retail_mode_dialog_body)
                 .setPositiveButton(R.string.exit_retail_mode_dialog_confirmation_button_text, this)
                 .setNegativeButton(android.R.string.cancel, null)
                 .create();
diff --git a/src/com/android/car/settings/users/ConfirmRemoveUserDialog.java b/src/com/android/car/settings/users/ConfirmRemoveUserDialog.java
deleted file mode 100644
index 2798a93..0000000
--- a/src/com/android/car/settings/users/ConfirmRemoveUserDialog.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.users;
-
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-
-import androidx.car.app.CarAlertDialog;
-
-import com.android.car.settings.R;
-
-/**
- * Dialog to confirm user removal.
- */
-public class ConfirmRemoveUserDialog extends DialogFragment {
-    private final DialogInterface.OnClickListener mDeleteUserListener = new OnClickListener() {
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            if (mListener != null && which == DialogInterface.BUTTON_POSITIVE) {
-                mListener.onRemoveUserConfirmed();
-            }
-            dialog.dismiss();
-        }
-    };
-
-    private ConfirmRemoveUserListener mListener;
-
-    /**
-     * Sets a listener for OnRemoveUserConfirmed that will get called if user confirms
-     * the dialog.
-     *
-     * @param listener Instance of {@link ConfirmRemoveUserListener} to call when confirmed.
-     */
-    public void setConfirmRemoveUserListener(ConfirmRemoveUserListener listener) {
-        mListener = listener;
-    }
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        return new CarAlertDialog.Builder(getContext())
-            .setTitle(R.string.really_remove_user_title)
-            .setBody(R.string.really_remove_user_message)
-            .setPositiveButton(R.string.delete_button, mDeleteUserListener)
-            .setNegativeButton(android.R.string.cancel, null)
-            .create();
-    }
-
-    /**
-     * Interface for listeners that want to receive a callback when user confirms user removal in a
-     * dialog.
-     */
-    public interface ConfirmRemoveUserListener {
-
-        /**
-         * Method called only when user presses delete button.
-         */
-        void onRemoveUserConfirmed();
-    }
-}
diff --git a/src/com/android/car/settings/users/EditUserNameEntryPreferenceController.java b/src/com/android/car/settings/users/EditUserNameEntryPreferenceController.java
new file mode 100644
index 0000000..0f72c48
--- /dev/null
+++ b/src/com/android/car/settings/users/EditUserNameEntryPreferenceController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.FragmentController;
+
+/** Business logic for the preference which opens the EditUserNameFragment. */
+public class EditUserNameEntryPreferenceController extends
+        UserDetailsBasePreferenceController<ButtonPreference> {
+
+    public EditUserNameEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ButtonPreference> getPreferenceType() {
+        return ButtonPreference.class;
+    }
+
+    @Override
+    protected void updateState(ButtonPreference preference) {
+        preference.setOnButtonClickListener(pref -> {
+            getFragmentController().launchFragment(EditUsernameFragment.newInstance(getUserInfo()));
+        });
+
+        Drawable icon = new UserIconProvider(getCarUserManagerHelper()).getUserIcon(getUserInfo(),
+                getContext());
+        preference.setIcon(icon);
+        preference.setTitle(UserUtils.getUserDisplayName(getContext(), getCarUserManagerHelper(),
+                getUserInfo()));
+
+        if (!getCarUserManagerHelper().isCurrentProcessUser(getUserInfo())) {
+            preference.showAction(false);
+        }
+        preference.setSummary(getSummary());
+    }
+
+    private CharSequence getSummary() {
+        if (!getUserInfo().isInitialized()) {
+            return getContext().getString(R.string.user_summary_not_set_up);
+        }
+        if (getUserInfo().isAdmin()) {
+            return getCarUserManagerHelper().isCurrentProcessUser(getUserInfo())
+                    ? getContext().getString(R.string.signed_in_admin_user)
+                    : getContext().getString(R.string.user_admin);
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/users/EditUsernameFragment.java b/src/com/android/car/settings/users/EditUsernameFragment.java
index 63e1476..03312c8 100644
--- a/src/com/android/car/settings/users/EditUsernameFragment.java
+++ b/src/com/android/car/settings/users/EditUsernameFragment.java
@@ -15,13 +15,20 @@
  */
 package com.android.car.settings.users;
 
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.provider.Settings;
-import android.support.design.widget.TextInputEditText;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
 import android.view.View;
 import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
 
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
@@ -31,10 +38,9 @@
  * Enables user to edit their username.
  */
 public class EditUsernameFragment extends BaseFragment {
-    public static final String EXTRA_USER_INFO = "extra_user_info";
     private UserInfo mUserInfo;
 
-    private TextInputEditText mUserNameEditText;
+    private EditText mUserNameEditText;
     private Button mOkButton;
     private Button mCancelButton;
     private CarUserManagerHelper mCarUserManagerHelper;
@@ -45,24 +51,39 @@
     public static EditUsernameFragment newInstance(UserInfo userInfo) {
         EditUsernameFragment
                 userSettingsFragment = new EditUsernameFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_TITLE_ID, R.string.edit_user_name_title);
-        bundle.putParcelable(EXTRA_USER_INFO, userInfo);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.edit_username_fragment);
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(Intent.EXTRA_USER, userInfo);
         userSettingsFragment.setArguments(bundle);
         return userSettingsFragment;
     }
 
     @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return R.layout.edit_username_fragment;
+    }
+
+    @Override
+    @StringRes
+    protected int getTitleId() {
+        return R.string.edit_user_name_title;
+    }
+
+    @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mUserInfo = getArguments().getParcelable(EXTRA_USER_INFO);
+        mUserInfo = getArguments().getParcelable(Intent.EXTRA_USER);
     }
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
-        mUserNameEditText = (TextInputEditText) view.findViewById(R.id.user_name_text_edit);
+        mUserNameEditText = view.findViewById(R.id.user_name_text_edit);
     }
 
     @Override
@@ -70,9 +91,9 @@
         super.onActivityCreated(savedInstanceState);
         mCarUserManagerHelper = new CarUserManagerHelper(getContext());
 
-        configureUsernameEditing();
         showOkButton();
         showCancelButton();
+        configureUsernameEditing();
     }
 
     private void configureUsernameEditing() {
@@ -80,11 +101,34 @@
         mUserNameEditText.setText(mUserInfo.name);
         mUserNameEditText.setEnabled(true);
         mUserNameEditText.setSelectAllOnFocus(true);
+        mUserNameEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                if (TextUtils.isEmpty(s)) {
+                    mOkButton.setEnabled(false);
+                    mUserNameEditText.setError(getString(R.string.name_input_blank_error));
+                } else if (!TextUtils.isGraphic(s)) {
+                    mOkButton.setEnabled(false);
+                    mUserNameEditText.setError(getString(R.string.name_input_invalid_error));
+                } else {
+                    mOkButton.setEnabled(true);
+                    mUserNameEditText.setError(null);
+                }
+            }
+
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+            }
+        });
     }
 
     private void showOkButton() {
         // Configure OK button.
-        mOkButton = (Button) getActivity().findViewById(R.id.action_button2);
+        mOkButton = getActivity().findViewById(R.id.action_button2);
         mOkButton.setVisibility(View.VISIBLE);
         mOkButton.setText(android.R.string.ok);
         mOkButton.setOnClickListener(view -> {
@@ -98,7 +142,7 @@
 
     private void showCancelButton() {
         // Configure Cancel button.
-        mCancelButton = (Button) getActivity().findViewById(R.id.action_button1);
+        mCancelButton = getActivity().findViewById(R.id.action_button1);
         mCancelButton.setVisibility(View.VISIBLE);
         mCancelButton.setText(android.R.string.cancel);
         mCancelButton.setOnClickListener(view -> {
diff --git a/src/com/android/car/settings/users/GuestFragment.java b/src/com/android/car/settings/users/GuestFragment.java
deleted file mode 100644
index eaec0d5..0000000
--- a/src/com/android/car/settings/users/GuestFragment.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.users;
-
-import android.car.user.CarUserManagerHelper;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows details for a guest session, including ability to switch to guest.
- */
-public class GuestFragment extends ListItemSettingsFragment {
-    private CarUserManagerHelper mCarUserManagerHelper;
-    private ListItemProvider mItemProvider;
-
-    /**
-     * Create new GuestFragment instance.
-     */
-    public static GuestFragment newInstance() {
-        GuestFragment guestFragment = new GuestFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.user_details_title);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        guestFragment.setArguments(bundle);
-        return guestFragment;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
-        mItemProvider = new ListItemProvider.ListProvider(getListItems());
-
-        // Super class's onActivityCreated need to be called after mItemProvider is initialized.
-        // Because getItemProvider is called in there.
-        super.onActivityCreated(savedInstanceState);
-
-        showSwitchButton();
-    }
-
-    private void showSwitchButton() {
-        Button switchUserBtn = (Button) getActivity().findViewById(R.id.action_button1);
-        // If the current process is not allowed to switch to another user, doe not show the switch
-        // button.
-        if (!mCarUserManagerHelper.canCurrentProcessSwitchUsers()) {
-            switchUserBtn.setVisibility(View.GONE);
-            return;
-        }
-        switchUserBtn.setVisibility(View.VISIBLE);
-        switchUserBtn.setText(R.string.user_switch);
-        switchUserBtn.setOnClickListener(v -> {
-            getActivity().onBackPressed();
-            mCarUserManagerHelper.startNewGuestSession(getContext().getString(R.string.user_guest));
-        });
-    }
-
-    private List<ListItem> getListItems() {
-        Drawable icon = UserIconProvider.scaleUserIcon(mCarUserManagerHelper.getGuestDefaultIcon(),
-                mCarUserManagerHelper, getContext());
-
-        TextListItem item = new TextListItem(getContext());
-        item.setPrimaryActionIcon(icon, /* useLargeIcon= */ false);
-        item.setTitle(getContext().getString(R.string.user_guest));
-
-        List<ListItem> items = new ArrayList<>();
-        items.add(item);
-        return items;
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        return mItemProvider;
-    }
-}
diff --git a/src/com/android/car/settings/users/MakeAdminPreferenceController.java b/src/com/android/car/settings/users/MakeAdminPreferenceController.java
new file mode 100644
index 0000000..0b93bbc
--- /dev/null
+++ b/src/com/android/car/settings/users/MakeAdminPreferenceController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+
+/** Business Logic for preference which promotes a regular user to an admin user. */
+public class MakeAdminPreferenceController extends
+        UserDetailsBasePreferenceController<ButtonPreference> {
+
+    @VisibleForTesting
+    final ConfirmationDialogFragment.ConfirmListener mConfirmListener =
+            arguments -> {
+                UserInfo userToMakeAdmin = (UserInfo) arguments.get(
+                        UsersDialogProvider.KEY_USER_TO_MAKE_ADMIN);
+                getCarUserManagerHelper().grantAdminPermissions(userToMakeAdmin);
+                getFragmentController().goBack();
+            };
+
+    public MakeAdminPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ButtonPreference> getPreferenceType() {
+        return ButtonPreference.class;
+    }
+
+
+    /** Ensure that the listener is reset if the dialog was open during a configuration change. */
+    @Override
+    protected void onCreateInternal() {
+        ConfirmationDialogFragment dialog =
+                (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
+                        ConfirmationDialogFragment.TAG);
+
+        ConfirmationDialogFragment.resetListeners(dialog, mConfirmListener, /* rejectListener= */
+                null);
+    }
+
+    @Override
+    protected void updateState(ButtonPreference preference) {
+        preference.setOnButtonClickListener(pref -> {
+
+            ConfirmationDialogFragment dialogFragment =
+                    UsersDialogProvider.getConfirmGrantAdminDialogFragment(getContext(),
+                            mConfirmListener, /* rejectListener= */ null, getUserInfo());
+
+            getFragmentController().showDialog(dialogFragment, ConfirmationDialogFragment.TAG);
+        });
+
+        Drawable icon = new UserIconProvider(getCarUserManagerHelper()).getUserIcon(getUserInfo(),
+                getContext());
+        preference.setIcon(icon);
+    }
+}
diff --git a/src/com/android/car/settings/users/MaxUsersLimitReachedDialog.java b/src/com/android/car/settings/users/MaxUsersLimitReachedDialog.java
new file mode 100644
index 0000000..4a9927e
--- /dev/null
+++ b/src/com/android/car/settings/users/MaxUsersLimitReachedDialog.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.R;
+
+/**
+ * Dialog to inform that user deletion failed and offers to retry.
+ */
+public class MaxUsersLimitReachedDialog extends DialogFragment {
+    @VisibleForTesting
+    static final String DIALOG_TAG = "MaxUsersLimitReachedDialog";
+    private final int mMaxUserLimit;
+
+    public MaxUsersLimitReachedDialog(int maxUserLimit) {
+        super();
+        mMaxUserLimit = maxUserLimit;
+    }
+
+    /**
+     * Shows the dialog.
+     *
+     * @param parent Fragment associated with the dialog.
+     */
+    public void show(Fragment parent) {
+        setTargetFragment(parent, 0);
+        show(parent.getFragmentManager(), DIALOG_TAG);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getContext())
+                .setTitle(R.string.user_limit_reached_title)
+                .setMessage(getResources().getQuantityString(
+                        R.plurals.user_limit_reached_message, mMaxUserLimit, mMaxUserLimit))
+                .setPositiveButton(android.R.string.ok, null)
+                .create();
+    }
+}
diff --git a/src/com/android/car/settings/users/PermissionsPreferenceController.java b/src/com/android/car/settings/users/PermissionsPreferenceController.java
new file mode 100644
index 0000000..c918dbb
--- /dev/null
+++ b/src/com/android/car/settings/users/PermissionsPreferenceController.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.UserManager;
+
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Constructs and populates the permissions toggles for non admin users.
+ *
+ * <p>In order to add a new permission, it needs to be added to {@link
+ * CarUserManagerHelper#OPTIONAL_NON_ADMIN_RESTRICTIONS} and the appropriate label needs to be added
+ * to {@link #PERMISSIONS_LIST}.
+ */
+public class PermissionsPreferenceController extends
+        UserDetailsBasePreferenceController<PreferenceGroup> {
+
+    private static class UserPermission {
+        private final String mPermissionKey;
+        @StringRes
+        private final int mPermissionTitle;
+
+        UserPermission(String key, int title) {
+            mPermissionKey = key;
+            mPermissionTitle = title;
+        }
+
+        public String getPermissionKey() {
+            return mPermissionKey;
+        }
+
+        public int getPermissionTitle() {
+            return mPermissionTitle;
+        }
+    }
+
+    @VisibleForTesting
+    static final String PERMISSION_TYPE_KEY = "permission_type_key";
+    private static final List<UserPermission> PERMISSIONS_LIST = new ArrayList<>();
+
+    // Add additional preferences to show here (in the order they should appear).
+    static {
+        PERMISSIONS_LIST.add(new UserPermission(UserManager.DISALLOW_ADD_USER,
+                R.string.create_user_permission_title));
+        PERMISSIONS_LIST.add(new UserPermission(UserManager.DISALLOW_OUTGOING_CALLS,
+                R.string.outgoing_calls_permission_title));
+        PERMISSIONS_LIST.add(new UserPermission(UserManager.DISALLOW_SMS,
+                R.string.sms_messaging_permission_title));
+        PERMISSIONS_LIST.add(new UserPermission(UserManager.DISALLOW_INSTALL_APPS,
+                R.string.install_apps_permission_title));
+        PERMISSIONS_LIST.add(new UserPermission(UserManager.DISALLOW_UNINSTALL_APPS,
+                R.string.uninstall_apps_permission_title));
+    }
+
+    private final List<SwitchPreference> mPermissionPreferences = new ArrayList<>();
+
+    public PermissionsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+
+        for (UserPermission permission : PERMISSIONS_LIST) {
+            SwitchPreference preference = new SwitchPreference(context);
+            preference.setTitle(permission.getPermissionTitle());
+            preference.getExtras().putString(PERMISSION_TYPE_KEY, permission.getPermissionKey());
+            preference.setOnPreferenceChangeListener((pref, newValue) -> {
+                boolean granted = (boolean) newValue;
+                getCarUserManagerHelper().setUserRestriction(getUserInfo(),
+                        pref.getExtras().getString(PERMISSION_TYPE_KEY), !granted);
+                return true;
+            });
+            mPermissionPreferences.add(preference);
+        }
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        for (SwitchPreference switchPreference : mPermissionPreferences) {
+            getPreference().addPreference(switchPreference);
+        }
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        for (SwitchPreference switchPreference : mPermissionPreferences) {
+            switchPreference.setChecked(!getCarUserManagerHelper().hasUserRestriction(
+                    switchPreference.getExtras().getString(PERMISSION_TYPE_KEY), getUserInfo()));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/users/RemoveUserErrorDialog.java b/src/com/android/car/settings/users/RemoveUserErrorDialog.java
deleted file mode 100644
index 32a3b36..0000000
--- a/src/com/android/car/settings/users/RemoveUserErrorDialog.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.users;
-
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.DialogFragment;
-
-import androidx.car.app.CarAlertDialog;
-
-import com.android.car.settings.R;
-
-/**
- * Dialog to inform that user deletion failed and offers to retry.
- */
-public class RemoveUserErrorDialog extends DialogFragment {
-    private DialogInterface.OnClickListener mRemoveUserRetryListener = new OnClickListener() {
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            if (mListener != null && which == DialogInterface.BUTTON_POSITIVE) {
-                mListener.onRetryRemoveUser();
-            }
-            dialog.dismiss();
-        }
-    };
-
-    private RemoveUserErrorListener mListener;
-
-    /**
-     * Sets a listener for onRetryRemoveUser that will get called if user presses positive
-     * button.
-     *
-     * @param listener Instance of {@link RemoveUserErrorListener} to call when confirmed.
-     */
-    public void setRetryListener(@Nullable RemoveUserErrorListener listener) {
-        mListener = listener;
-    }
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        return new CarAlertDialog.Builder(getContext())
-                .setTitle(R.string.remove_user_error_title)
-                .setBody(R.string.remove_user_error_message)
-                .setPositiveButton(R.string.remove_user_error_retry, mRemoveUserRetryListener)
-                .setNegativeButton(R.string.remove_user_error_dismiss, null)
-                .create();
-    }
-
-    /**
-     * Interface for listeners that want to receive a callback when user removal fails.
-     */
-    public interface RemoveUserErrorListener {
-
-        /**
-         * Method called only when user presses retry button.
-         */
-        void onRetryRemoveUser();
-    }
-}
diff --git a/src/com/android/car/settings/users/UserDetailsBaseFragment.java b/src/com/android/car/settings/users/UserDetailsBaseFragment.java
new file mode 100644
index 0000000..2206ec9
--- /dev/null
+++ b/src/com/android/car/settings/users/UserDetailsBaseFragment.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.LayoutRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.ErrorDialog;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Common logic shared for controlling the action bar which contains a button to delete a user. */
+public abstract class UserDetailsBaseFragment extends SettingsFragment {
+
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private UserInfo mUserInfo;
+
+    private final ConfirmationDialogFragment.ConfirmListener mConfirmListener = arguments -> {
+        String userType = arguments.getString(UsersDialogProvider.KEY_USER_TYPE);
+        if (userType.equals(UsersDialogProvider.LAST_ADMIN)) {
+            launchFragment(ChooseNewAdminFragment.newInstance(mUserInfo));
+        } else {
+            if (mCarUserManagerHelper.removeUser(
+                    mUserInfo, getContext().getString(R.string.user_guest))) {
+                getActivity().onBackPressed();
+            } else {
+                // If failed, need to show error dialog for users.
+                ErrorDialog.show(this, R.string.delete_user_error_title);
+            }
+        }
+    };
+
+    /** Adds user id to fragment arguments. */
+    protected static UserDetailsBaseFragment addUserIdToFragmentArguments(
+            UserDetailsBaseFragment fragment, int userId) {
+        Bundle bundle = new Bundle();
+        bundle.putInt(Intent.EXTRA_USER_ID, userId);
+        fragment.setArguments(bundle);
+        return fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        int userId = getArguments().getInt(Intent.EXTRA_USER_ID);
+        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
+        mUserInfo = UserUtils.getUserInfo(getContext(), userId);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        ConfirmationDialogFragment dialogFragment =
+                (ConfirmationDialogFragment) findDialogByTag(ConfirmationDialogFragment.TAG);
+        ConfirmationDialogFragment.resetListeners(dialogFragment,
+                mConfirmListener, /* rejectListener= */ null);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        showRemoveUserButton();
+
+        TextView titleView = getActivity().findViewById(R.id.title);
+        titleView.setText(getTitleText());
+    }
+
+    /** Make CarUserManagerHelper available to subclasses. */
+    protected CarUserManagerHelper getCarUserManagerHelper() {
+        return mCarUserManagerHelper;
+    }
+
+    /** Make UserInfo available to subclasses. */
+    protected UserInfo getUserInfo() {
+        return mUserInfo;
+    }
+
+    /** Refresh UserInfo in case it becomes invalid. */
+    protected void refreshUserInfo() {
+        mUserInfo = UserUtils.getUserInfo(getContext(), mUserInfo.id);
+    }
+
+    /** Defines the text that should be shown in the action bar. */
+    protected abstract String getTitleText();
+
+    private void showRemoveUserButton() {
+        Button removeUserBtn = getActivity().findViewById(R.id.action_button1);
+        // If the current user is not allowed to remove users, the user trying to be removed
+        // cannot be removed, or the current user is a demo user, do not show delete button.
+        if (!mCarUserManagerHelper.canCurrentProcessRemoveUsers()
+                || !mCarUserManagerHelper.canUserBeRemoved(mUserInfo)
+                || mCarUserManagerHelper.isCurrentProcessDemoUser()) {
+            removeUserBtn.setVisibility(View.GONE);
+            return;
+        }
+        removeUserBtn.setVisibility(View.VISIBLE);
+        removeUserBtn.setText(R.string.delete_button);
+        removeUserBtn.setOnClickListener(v -> showConfirmRemoveUserDialog());
+    }
+
+    private void showConfirmRemoveUserDialog() {
+        boolean isLastUser = mCarUserManagerHelper.getAllPersistentUsers().size() == 1;
+        boolean isLastAdmin = mUserInfo.isAdmin()
+                && mCarUserManagerHelper.getAllAdminUsers().size() == 1;
+
+        ConfirmationDialogFragment dialogFragment;
+
+        if (isLastUser) {
+            dialogFragment = UsersDialogProvider.getConfirmRemoveLastUserDialogFragment(
+                    getContext(), mConfirmListener, /* rejectListener= */ null);
+        } else if (isLastAdmin) {
+            dialogFragment = UsersDialogProvider.getConfirmRemoveLastAdminDialogFragment(
+                    getContext(), mConfirmListener, /* rejectListener= */ null);
+        } else {
+            dialogFragment = UsersDialogProvider.getConfirmRemoveUserDialogFragment(getContext(),
+                    mConfirmListener, /* rejectListener= */ null);
+        }
+
+        dialogFragment.show(getFragmentManager(), ConfirmationDialogFragment.TAG);
+    }
+}
diff --git a/src/com/android/car/settings/users/UserDetailsBasePreferenceController.java b/src/com/android/car/settings/users/UserDetailsBasePreferenceController.java
new file mode 100644
index 0000000..73b2969
--- /dev/null
+++ b/src/com/android/car/settings/users/UserDetailsBasePreferenceController.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Common setup of all preference controllers related to user details.
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller
+ *            expects to operate.
+ */
+public abstract class UserDetailsBasePreferenceController<V extends Preference> extends
+        PreferenceController<V> {
+
+    private final CarUserManagerHelper.OnUsersUpdateListener mOnUsersUpdateListener = () -> {
+        refreshUserInfo();
+        refreshUi();
+    };
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private UserInfo mUserInfo;
+
+    public UserDetailsBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
+    }
+
+    /** Sets the user info for which this preference controller operates. */
+    public void setUserInfo(UserInfo userInfo) {
+        mUserInfo = userInfo;
+    }
+
+    /** Gets the current user info. */
+    public UserInfo getUserInfo() {
+        return mUserInfo;
+    }
+
+    /** Refreshes the user info, since it might have changed. */
+    protected void refreshUserInfo() {
+        mUserInfo = UserUtils.getUserInfo(getContext(), mUserInfo.id);
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mUserInfo == null) {
+            throw new IllegalStateException("UserInfo should be non-null by this point");
+        }
+    }
+
+    /** Registers a listener which updates the displayed user name when a user is modified. */
+    @Override
+    protected void onCreateInternal() {
+        getCarUserManagerHelper().registerOnUsersUpdateListener(mOnUsersUpdateListener);
+    }
+
+    /** Unregisters a listener which updates the displayed user name when a user is modified. */
+    @Override
+    protected void onDestroyInternal() {
+        getCarUserManagerHelper().unregisterOnUsersUpdateListener(mOnUsersUpdateListener);
+    }
+
+    /** Gets the car user manager helper. */
+    protected CarUserManagerHelper getCarUserManagerHelper() {
+        return mCarUserManagerHelper;
+    }
+}
diff --git a/src/com/android/car/settings/users/UserDetailsFragment.java b/src/com/android/car/settings/users/UserDetailsFragment.java
index 26ab438..304df28 100644
--- a/src/com/android/car/settings/users/UserDetailsFragment.java
+++ b/src/com/android/car/settings/users/UserDetailsFragment.java
@@ -16,179 +16,62 @@
 
 package com.android.car.settings.users;
 
-import android.car.user.CarUserManagerHelper;
-import android.content.pm.UserInfo;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
 import android.os.Bundle;
-import android.support.annotation.VisibleForTesting;
-import android.view.View;
-import android.widget.Button;
+import android.widget.TextView;
 
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
-
-import java.util.Arrays;
-import java.util.List;
 
 /**
- * Shows details for a user with the ability to remove user and switch.
+ * Shows details for a user with the ability to remove user and edit current user.
  */
-public class UserDetailsFragment extends ListItemSettingsFragment implements
-        ConfirmRemoveUserDialog.ConfirmRemoveUserListener,
-        RemoveUserErrorDialog.RemoveUserErrorListener {
-    public static final String EXTRA_USER_INFO = "extra_user_info";
-    private static final String ERROR_DIALOG_TAG = "RemoveUserErrorDialogTag";
-    private static final String CONFIRM_REMOVE_DIALOG_TAG = "ConfirmRemoveUserDialog";
+public class UserDetailsFragment extends UserDetailsBaseFragment {
 
-    private Button mRemoveUserBtn;
-    private Button mSwitchUserBtn;
+    /** Creates instance of UserDetailsFragment. */
+    public static UserDetailsFragment newInstance(int userId) {
+        return (UserDetailsFragment) UserDetailsBaseFragment.addUserIdToFragmentArguments(
+                new UserDetailsFragment(), userId);
+    }
 
-    private UserInfo mUserInfo;
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    CarUserManagerHelper mCarUserManagerHelper;
-    private UserIconProvider mUserIconProvider;
-    private ListItemProvider mItemProvider;
+    @VisibleForTesting
+    final CarUserManagerHelper.OnUsersUpdateListener mOnUsersUpdateListener = () -> {
+        // Update the user info value, as it may have changed.
+        refreshUserInfo();
+        // Update the text in the action bar when there is a user update.
+        ((TextView) getActivity().findViewById(R.id.title)).setText(getTitleText());
+    };
 
-    /**
-     * Creates instance of UserDetailsFragment.
-     */
-    public static UserDetailsFragment newInstance(UserInfo userInfo) {
-        UserDetailsFragment userDetailsFragment = new UserDetailsFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_TITLE_ID, R.string.user_details_title);
-        bundle.putParcelable(EXTRA_USER_INFO, userInfo);
-        userDetailsFragment.setArguments(bundle);
-        return userDetailsFragment;
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.user_details_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        use(EditUserNameEntryPreferenceController.class,
+                R.string.pk_edit_user_name_entry).setUserInfo(getUserInfo());
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mUserInfo = getArguments().getParcelable(EXTRA_USER_INFO);
-
-        if (savedInstanceState != null) {
-            RemoveUserErrorDialog removeUserErrorDialog = (RemoveUserErrorDialog)
-                    getFragmentManager().findFragmentByTag(ERROR_DIALOG_TAG);
-            if (removeUserErrorDialog != null) {
-                removeUserErrorDialog.setRetryListener(this);
-            }
-
-            ConfirmRemoveUserDialog confirmRemoveUserDialog = (ConfirmRemoveUserDialog)
-                    getFragmentManager().findFragmentByTag(CONFIRM_REMOVE_DIALOG_TAG);
-            if (confirmRemoveUserDialog != null) {
-                confirmRemoveUserDialog.setConfirmRemoveUserListener(this);
-            }
-        }
+        getCarUserManagerHelper().registerOnUsersUpdateListener(mOnUsersUpdateListener);
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        createUserManagerHelper();
-        mUserIconProvider = new UserIconProvider(mCarUserManagerHelper);
-        mItemProvider = new ListItemProvider.ListProvider(getListItems());
-
-        // Needs to be called after creation of item provider.
-        super.onActivityCreated(savedInstanceState);
-
-        showActionButtons();
+    public void onDestroy() {
+        super.onDestroy();
+        getCarUserManagerHelper().unregisterOnUsersUpdateListener(mOnUsersUpdateListener);
     }
 
     @Override
-    public void onRemoveUserConfirmed() {
-        removeUser();
-    }
-
-    @Override
-    public void onRetryRemoveUser() {
-        // Retry deleting user.
-        removeUser();
-    }
-
-    @Override
-    public ListItemProvider getItemProvider() {
-        return mItemProvider;
-    }
-
-    private void removeUser() {
-        if (mCarUserManagerHelper.removeUser(
-                mUserInfo, getContext().getString(R.string.user_guest))) {
-            getActivity().onBackPressed();
-        } else {
-            // If failed, need to show error dialog for users, can offer retry.
-            RemoveUserErrorDialog removeUserErrorDialog = new RemoveUserErrorDialog();
-            removeUserErrorDialog.setRetryListener(this);
-            removeUserErrorDialog.show(getFragmentManager(), ERROR_DIALOG_TAG);
-        }
-    }
-
-    private void showActionButtons() {
-        if (mCarUserManagerHelper.isForegroundUser(mUserInfo)) {
-            // Already foreground user, shouldn't show SWITCH button.
-            showRemoveUserButton();
-            return;
-        }
-
-        showRemoveUserButton();
-        showSwitchButton();
-    }
-
-    private void createUserManagerHelper() {
-        // Null check for testing. Don't want to override it if already set by a test.
-        if (mCarUserManagerHelper == null) {
-            mCarUserManagerHelper = new CarUserManagerHelper(getContext());
-        }
-    }
-
-    private void showRemoveUserButton() {
-        Button removeUserBtn = (Button) getActivity().findViewById(R.id.action_button1);
-        // If the current user is not allowed to remove users, the user trying to be removed
-        // cannot be removed, or the current user is a demo user, do not show delete button.
-        if (!mCarUserManagerHelper.canCurrentProcessRemoveUsers()
-                || !mCarUserManagerHelper.canUserBeRemoved(mUserInfo)
-                || mCarUserManagerHelper.isCurrentProcessDemoUser()) {
-            removeUserBtn.setVisibility(View.GONE);
-            return;
-        }
-        removeUserBtn.setVisibility(View.VISIBLE);
-        removeUserBtn.setText(R.string.delete_button);
-        removeUserBtn.setOnClickListener(v -> {
-            ConfirmRemoveUserDialog dialog = new ConfirmRemoveUserDialog();
-            dialog.setConfirmRemoveUserListener(this);
-            dialog.show(getFragmentManager(), CONFIRM_REMOVE_DIALOG_TAG);
-        });
-    }
-
-    private void showSwitchButton() {
-        Button switchUserBtn = (Button) getActivity().findViewById(R.id.action_button2);
-        // If the current process is not allowed to switch to another user, doe not show the switch
-        // button.
-        if (!mCarUserManagerHelper.canCurrentProcessSwitchUsers()) {
-            switchUserBtn.setVisibility(View.GONE);
-            return;
-        }
-        switchUserBtn.setVisibility(View.VISIBLE);
-        switchUserBtn.setText(R.string.user_switch);
-        switchUserBtn.setOnClickListener(v -> {
-            mCarUserManagerHelper.switchToUser(mUserInfo);
-            getActivity().onBackPressed();
-        });
-    }
-
-    private List<ListItem> getListItems() {
-        TextListItem item = new TextListItem(getContext());
-        item.setPrimaryActionIcon(mUserIconProvider.getUserIcon(mUserInfo, getContext()),
-                /* useLargeIcon= */ false);
-        item.setTitle(mUserInfo.name);
-
-        if (!mUserInfo.isInitialized()) {
-            // Indicate that the user has not been initialized yet.
-            item.setBody(getContext().getString(R.string.user_summary_not_set_up));
-        }
-
-        return Arrays.asList(item);
+    protected String getTitleText() {
+        return UserUtils.getUserDisplayName(getContext(), getCarUserManagerHelper(), getUserInfo());
     }
 }
diff --git a/src/com/android/car/settings/users/UserDetailsPermissionsFragment.java b/src/com/android/car/settings/users/UserDetailsPermissionsFragment.java
new file mode 100644
index 0000000..814e2a2
--- /dev/null
+++ b/src/com/android/car/settings/users/UserDetailsPermissionsFragment.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.content.Context;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+
+/**
+ * Business logic for the permissions fragment. This fragment is used when an admin user views the
+ * user details of a non-admin user.
+ */
+public class UserDetailsPermissionsFragment extends UserDetailsBaseFragment {
+
+    /**
+     * Creates instance of UserDetailsPermissionsFragment.
+     */
+    public static UserDetailsPermissionsFragment newInstance(int userId) {
+        return (UserDetailsPermissionsFragment) UserDetailsBaseFragment
+                .addUserIdToFragmentArguments(
+                new UserDetailsPermissionsFragment(), userId);
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.user_details_permissions_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        use(MakeAdminPreferenceController.class, R.string.pk_make_user_admin).setUserInfo(
+                getUserInfo());
+        use(PermissionsPreferenceController.class, R.string.pk_user_permissions).setUserInfo(
+                getUserInfo());
+    }
+
+    @Override
+    protected String getTitleText() {
+        return getContext().getString(R.string.user_details_admin_title,
+                UserUtils.getUserDisplayName(getContext(), getCarUserManagerHelper(),
+                        getUserInfo()));
+    }
+}
+
diff --git a/src/com/android/car/settings/users/UserGridRecyclerView.java b/src/com/android/car/settings/users/UserGridRecyclerView.java
index 3a2ce29..bf9492b 100644
--- a/src/com/android/car/settings/users/UserGridRecyclerView.java
+++ b/src/com/android/car/settings/users/UserGridRecyclerView.java
@@ -16,14 +16,12 @@
 
 package com.android.car.settings.users;
 
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
-import android.support.v7.widget.RecyclerView;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -32,12 +30,15 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import androidx.car.widget.PagedListView;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.users.ConfirmCreateNewUserDialog.CancelCreateNewUserListener;
-import com.android.car.settings.users.ConfirmCreateNewUserDialog.ConfirmCreateNewUserListener;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.ErrorDialog;
 import com.android.internal.util.UserIcons;
 
 import java.util.ArrayList;
@@ -47,8 +48,9 @@
  * Displays a GridLayout with icons for the users in the system to allow switching between users.
  * One of the uses of this is for the lock screen in auto.
  */
-public class UserGridRecyclerView extends PagedListView implements
+public class UserGridRecyclerView extends RecyclerView implements
         CarUserManagerHelper.OnUsersUpdateListener {
+
     private UserAdapter mAdapter;
     private CarUserManagerHelper mCarUserManagerHelper;
     private Context mContext;
@@ -61,6 +63,9 @@
         mContext = context;
         mCarUserManagerHelper = new CarUserManagerHelper(mContext);
         mEnableAddUserButton = true;
+
+        addItemDecoration(new ItemSpacingDecoration(context.getResources().getDimensionPixelSize(
+                R.dimen.user_switcher_vertical_spacing_between_users)));
     }
 
     /**
@@ -96,31 +101,46 @@
 
     private List<UserRecord> createUserRecords(List<UserInfo> userInfoList) {
         List<UserRecord> userRecords = new ArrayList<>();
+
+        // If the foreground user CANNOT switch to other users, only display the foreground user.
+        if (!mCarUserManagerHelper.canForegroundUserSwitchUsers()) {
+            userRecords.add(createForegroundUserRecord());
+            return userRecords;
+        }
+
+        // If the foreground user CAN switch to other users, iterate through all users.
         for (UserInfo userInfo : userInfoList) {
-            if (userInfo.isGuest()) {
-                // Don't display guests in the switcher.
-                continue;
-            }
             boolean isForeground =
                     mCarUserManagerHelper.getCurrentForegroundUserId() == userInfo.id;
+
+            if (!isForeground && userInfo.isGuest()) {
+                // Don't display temporary running background guests in the switcher.
+                continue;
+            }
+
             UserRecord record = new UserRecord(userInfo, false /* isStartGuestSession */,
                     false /* isAddUser */, isForeground);
             userRecords.add(record);
         }
 
-        // Add guest user record if the foreground user is not a guest
+        // Add start guest user record if the system is not logged in as guest already.
         if (!mCarUserManagerHelper.isForegroundUserGuest()) {
-            userRecords.add(addGuestUserRecord());
+            userRecords.add(createStartGuestUserRecord());
         }
 
         // Add "add user" record if the foreground user can add users
         if (mCarUserManagerHelper.canForegroundUserAddUsers()) {
-            userRecords.add(addUserRecord());
+            userRecords.add(createAddUserRecord());
         }
 
         return userRecords;
     }
 
+    private UserRecord createForegroundUserRecord() {
+        return new UserRecord(mCarUserManagerHelper.getCurrentForegroundUserInfo(),
+                false /* isStartGuestSession */, false /* isAddUser */, true /* isForeground */);
+    }
+
     /**
      * Show the "Add User" Button
      */
@@ -140,9 +160,9 @@
     /**
      * Create guest user record
      */
-    private UserRecord addGuestUserRecord() {
+    private UserRecord createStartGuestUserRecord() {
         UserInfo userInfo = new UserInfo();
-        userInfo.name = mContext.getString(R.string.user_guest);
+        userInfo.name = mContext.getString(R.string.start_guest_session);
         return new UserRecord(userInfo, true /* isStartGuestSession */,
                 false /* isAddUser */, false /* isForeground */);
     }
@@ -150,7 +170,7 @@
     /**
      * Create add user record
      */
-    private UserRecord addUserRecord() {
+    private UserRecord createAddUserRecord() {
         UserInfo userInfo = new UserInfo();
         userInfo.name = mContext.getString(R.string.user_add_user_menu);
         return new UserRecord(userInfo, false /* isStartGuestSession */,
@@ -164,7 +184,7 @@
     @Override
     public void onUsersUpdate() {
         // If you can show the add user button, there is no restriction
-        mAdapter.setAddUserRestricted(mEnableAddUserButton ? false : true);
+        mAdapter.setAddUserRestricted(!mEnableAddUserButton);
         mAdapter.clearUsers();
         mAdapter.updateUsers(createUserRecords(mCarUserManagerHelper
                 .getAllUsers()));
@@ -175,20 +195,33 @@
      * Adapter to populate the grid layout with the available user profiles
      */
     public final class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserAdapterViewHolder>
-            implements ConfirmCreateNewUserListener, CancelCreateNewUserListener,
-            AddNewUserTask.AddNewUserListener {
+            implements AddNewUserTask.AddNewUserListener {
 
         private final Context mContext;
-        private List<UserRecord> mUsers;
         private final Resources mRes;
         private final String mGuestName;
-        private final String mNewUserName;
+
+        private List<UserRecord> mUsers;
+        private String mNewUserName;
         // View that holds the add user button.  Used to enable/disable the view
         private View mAddUserView;
         private float mOpacityDisabled;
         private float mOpacityEnabled;
         private boolean mIsAddUserRestricted;
 
+        private final ConfirmationDialogFragment.ConfirmListener mConfirmListener = arguments -> {
+            mAddNewUserTask = new AddNewUserTask(mCarUserManagerHelper, /* addNewUserListener= */
+                    this);
+            mAddNewUserTask.execute(mNewUserName);
+        };
+
+        /**
+         * Enable the "add user" button if the user cancels adding an user
+         */
+        private final ConfirmationDialogFragment.RejectListener mRejectListener =
+                arguments -> enableAddView();
+
+
         public UserAdapter(Context context, List<UserRecord> users) {
             mRes = context.getResources();
             mContext = context;
@@ -208,7 +241,6 @@
 
         /**
          * Refreshes the User Grid with the new List of users.
-         * @param users
          */
         public void updateUsers(List<UserRecord> users) {
             mUsers = users;
@@ -217,7 +249,7 @@
         @Override
         public UserAdapterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
             View view = LayoutInflater.from(mContext)
-                    .inflate(R.layout.car_user_switcher_pod, parent, false);
+                    .inflate(R.layout.user_switcher_pod, parent, false);
             view.setAlpha(mOpacityEnabled);
             view.bringToFront();
             return new UserAdapterViewHolder(view);
@@ -232,100 +264,117 @@
             holder.mUserAvatarImageView.setImageDrawable(circleIcon);
             holder.mUserNameTextView.setText(userRecord.mInfo.name);
 
-            // Show the current user frame if current user
+            // Defaults to 100% opacity and no circle around the icon.
+            holder.mView.setAlpha(mOpacityEnabled);
+            holder.mFrame.setBackgroundResource(0);
+
+            // Foreground user record.
             if (userRecord.mIsForeground) {
-                holder.mFrame.setBackgroundResource(R.drawable.car_user_avatar_bg_circle);
-            } else {
-                holder.mFrame.setBackgroundResource(0);
+                // Add a circle around the icon.
+                holder.mFrame.setBackgroundResource(R.drawable.user_avatar_bg_circle);
+                // Go back to quick settings if user selected is already the foreground user.
+                holder.mView.setOnClickListener(v -> mBaseFragment.getActivity().onBackPressed());
+                return;
             }
 
-            // If there are restrictions, show a 50% opaque "add user" view
-            if (userRecord.mIsAddUser && mIsAddUserRestricted) {
-                holder.mView.setAlpha(mOpacityDisabled);
-            } else {
-                holder.mView.setAlpha(mOpacityEnabled);
+            // Start guest session record.
+            if (userRecord.mIsStartGuestSession) {
+                holder.mView.setOnClickListener(v -> handleGuestSessionClicked());
+                return;
             }
 
-            holder.mView.setOnClickListener(v -> {
-                if (userRecord == null) {
-                    return;
+            // Add user record.
+            if (userRecord.mIsAddUser) {
+                if (mIsAddUserRestricted) {
+                    // If there are restrictions, show a 50% opaque "add user" view
+                    holder.mView.setAlpha(mOpacityDisabled);
+                    holder.mView.setOnClickListener(
+                            v -> mBaseFragment.getFragmentController().showBlockingMessage());
+                } else {
+                    holder.mView.setOnClickListener(v -> handleAddUserClicked(v));
                 }
+                return;
+            }
 
-                // If the user selects Guest, start the guest session.
-                if (userRecord.mIsStartGuestSession) {
-                    mCarUserManagerHelper.startNewGuestSession(mGuestName);
-                    return;
-                }
-
-                // If the user wants to add a user, show dialog to confirm adding a user
-                if (userRecord.mIsAddUser) {
-                    if (mIsAddUserRestricted) {
-                        mBaseFragment.getFragmentController().showDOBlockingMessage();
-                    } else {
-                        // Disable button so it cannot be clicked multiple times
-                        mAddUserView = holder.mView;
-                        mAddUserView.setEnabled(false);
-
-                        ConfirmCreateNewUserDialog dialog =
-                                new ConfirmCreateNewUserDialog();
-                        dialog.setConfirmCreateNewUserListener(this);
-                        dialog.setCancelCreateNewUserListener(this);
-                        if (mBaseFragment != null) {
-                            dialog.show(mBaseFragment);
-                        }
-                    }
-                    return;
-                }
-                // If the user doesn't want to be a guest or add a user, switch to the user selected
-                mCarUserManagerHelper.switchToUser(userRecord.mInfo);
-            });
-
+            // User record;
+            holder.mView.setOnClickListener(v -> handleUserSwitch(userRecord.mInfo));
         }
 
         /**
          * Specify if adding a user should be restricted.
          *
          * @param isAddUserRestricted should adding a user be restricted
-         *
          */
         public void setAddUserRestricted(boolean isAddUserRestricted) {
             mIsAddUserRestricted = isAddUserRestricted;
         }
 
+        private void handleUserSwitch(UserInfo userInfo) {
+            if (mCarUserManagerHelper.switchToUser(userInfo)) {
+                // Successful switch, close Settings app.
+                mBaseFragment.getActivity().finish();
+            }
+        }
+
+        private void handleGuestSessionClicked() {
+            if (mCarUserManagerHelper.startGuestSession(mGuestName)) {
+                // Successful start, will switch to guest now. Close Settings app.
+                mBaseFragment.getActivity().finish();
+            }
+        }
+
+        private void handleAddUserClicked(View addUserView) {
+            if (mCarUserManagerHelper.isUserLimitReached()) {
+                showMaxUsersLimitReachedDialog();
+            } else {
+                mAddUserView = addUserView;
+                // Disable button so it cannot be clicked multiple times
+                mAddUserView.setEnabled(false);
+                showConfirmCreateNewUserDialog();
+            }
+        }
+
+        private void showMaxUsersLimitReachedDialog() {
+            MaxUsersLimitReachedDialog dialog = new MaxUsersLimitReachedDialog(
+                    mCarUserManagerHelper.getMaxSupportedRealUsers());
+            if (mBaseFragment != null) {
+                dialog.show(mBaseFragment);
+            }
+        }
+
+        private void showConfirmCreateNewUserDialog() {
+            ConfirmationDialogFragment dialogFragment =
+                    UsersDialogProvider.getConfirmCreateNewUserDialogFragment(getContext(),
+                            mConfirmListener, mRejectListener);
+            dialogFragment.show(mBaseFragment.getFragmentManager(), ConfirmationDialogFragment.TAG);
+        }
+
         private Bitmap getUserRecordIcon(UserRecord userRecord) {
             if (userRecord.mIsStartGuestSession) {
                 return mCarUserManagerHelper.getGuestDefaultIcon();
             }
 
             if (userRecord.mIsAddUser) {
-                return UserIcons.convertToBitmap(mContext
-                    .getDrawable(R.drawable.car_add_circle_round));
+                return UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.user_add_circle));
             }
 
             return mCarUserManagerHelper.getUserIcon(userRecord.mInfo);
         }
 
-        @Override
-        public void onCreateNewUserConfirmed() {
-            mAddNewUserTask =
-                    new AddNewUserTask(mCarUserManagerHelper, /* addNewUserListener= */ this);
-            mAddNewUserTask.execute(mNewUserName);
-        }
 
-        /**
-         * Enable the "add user" button if the user cancels adding an user
-         */
         @Override
-        public void onCreateNewUserCancelled() {
-            if (mAddUserView != null) {
-                mAddUserView.setEnabled(true);
-            }
+        public void onUserAddedSuccess() {
+            enableAddView();
+            // New user added. Will switch to new user, therefore close the app.
+            mBaseFragment.getActivity().finish();
         }
 
         @Override
-        public void onUserAdded() {
-            if (mAddUserView != null) {
-                mAddUserView.setEnabled(true);
+        public void onUserAddedFailure() {
+            enableAddView();
+            // Display failure dialog.
+            if (mBaseFragment != null) {
+                ErrorDialog.show(mBaseFragment, R.string.add_user_error_title);
             }
         }
 
@@ -347,9 +396,15 @@
             public UserAdapterViewHolder(View view) {
                 super(view);
                 mView = view;
-                mUserAvatarImageView = (ImageView) view.findViewById(R.id.user_avatar);
-                mUserNameTextView = (TextView) view.findViewById(R.id.user_name);
-                mFrame = (FrameLayout) view.findViewById(R.id.current_user_frame);
+                mUserAvatarImageView = view.findViewById(R.id.user_avatar);
+                mUserNameTextView = view.findViewById(R.id.user_name);
+                mFrame = view.findViewById(R.id.current_user_frame);
+            }
+        }
+
+        private void enableAddView() {
+            if (mAddUserView != null) {
+                mAddUserView.setEnabled(true);
             }
         }
     }
@@ -373,4 +428,31 @@
             mIsForeground = isForeground;
         }
     }
+
+    /**
+     * A {@link RecyclerView.ItemDecoration} that will add spacing between each item in the
+     * RecyclerView that it is added to.
+     */
+    private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration {
+        private int mItemSpacing;
+
+        private ItemSpacingDecoration(int itemSpacing) {
+            mItemSpacing = itemSpacing;
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                RecyclerView.State state) {
+            super.getItemOffsets(outRect, view, parent, state);
+            int position = parent.getChildAdapterPosition(view);
+
+            // Skip offset for last item except for GridLayoutManager.
+            if (position == state.getItemCount() - 1
+                    && !(parent.getLayoutManager() instanceof GridLayoutManager)) {
+                return;
+            }
+
+            outRect.bottom = mItemSpacing;
+        }
+    }
 }
diff --git a/src/com/android/car/settings/users/UserIconProvider.java b/src/com/android/car/settings/users/UserIconProvider.java
index ff9e691..f08ef66 100644
--- a/src/com/android/car/settings/users/UserIconProvider.java
+++ b/src/com/android/car/settings/users/UserIconProvider.java
@@ -16,15 +16,16 @@
 
 package com.android.car.settings.users;
 
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 
 import com.android.car.settings.R;
 
@@ -43,7 +44,6 @@
      * If icon has not been assigned to this user, it defaults to a generic user icon.
      *
      * @param userInfo User for which the icon is requested.
-     *
      * @return Drawable representing the icon for the user.
      */
     public Drawable getUserIcon(UserInfo userInfo, Context context) {
@@ -54,7 +54,7 @@
         }
         Resources res = context.getResources();
         BitmapDrawable scaledIcon = (BitmapDrawable) mCarUserManagerHelper.scaleUserIcon(icon, res
-                .getDimensionPixelSize(R.dimen.car_primary_icon_size));
+                .getDimensionPixelSize(R.dimen.icon_size));
 
         // Enforce that the icon is circular
         RoundedBitmapDrawable circleIcon = RoundedBitmapDrawableFactory
@@ -64,15 +64,24 @@
     }
 
     /**
+     * Gets the default icon for guest user.
+     *
+     * @return Drawable representing the default guest icon.
+     */
+    public Drawable getDefaultGuestIcon(Context context) {
+        return UserIconProvider.scaleUserIcon(mCarUserManagerHelper.getGuestDefaultIcon(),
+                mCarUserManagerHelper, context);
+    }
+
+    /**
      * Scales passed in bitmap to the appropriate user icon size.
      *
      * @param bitmap Bitmap to scale.
-     *
      * @return Drawable scaled to the user icon size.
      */
     public static Drawable scaleUserIcon(Bitmap bitmap, CarUserManagerHelper userManagerHelper,
             Context context) {
         return userManagerHelper.scaleUserIcon(bitmap, context.getResources()
-                .getDimensionPixelSize(R.dimen.car_primary_icon_size));
+                .getDimensionPixelSize(R.dimen.icon_size));
     }
 }
diff --git a/src/com/android/car/settings/users/UserSwitcherFragment.java b/src/com/android/car/settings/users/UserSwitcherFragment.java
index bb84f71..bf18bdd 100644
--- a/src/com/android/car/settings/users/UserSwitcherFragment.java
+++ b/src/com/android/car/settings/users/UserSwitcherFragment.java
@@ -18,7 +18,10 @@
 
 import android.car.drivingstate.CarUxRestrictions;
 import android.os.Bundle;
-import android.support.v7.widget.GridLayoutManager;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
+import androidx.recyclerview.widget.GridLayoutManager;
 
 import com.android.car.settings.R;
 import com.android.car.settings.common.BaseFragment;
@@ -31,18 +34,22 @@
 
     private UserGridRecyclerView mUserGridView;
 
-    /**
-     * Initializes the UserSwitcherFragment
-     * @return instance of the UserSwitcherFragment
-     */
-    public static UserSwitcherFragment newInstance() {
-        UserSwitcherFragment userSwitcherFragment = new UserSwitcherFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.users_list_title);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.car_user_switcher);
-        userSwitcherFragment.setArguments(bundle);
-        return userSwitcherFragment;
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getLayoutId() {
+        return R.layout.user_switcher;
+    }
+
+    @Override
+    @StringRes
+    protected int getTitleId() {
+        return R.string.users_list_title;
     }
 
     @Override
@@ -53,7 +60,7 @@
         GridLayoutManager layoutManager = new GridLayoutManager(getContext(),
                 getContext().getResources().getInteger(R.integer.user_switcher_num_col));
         mUserGridView.setFragment(this);
-        mUserGridView.getRecyclerView().setLayoutManager(layoutManager);
+        mUserGridView.setLayoutManager(layoutManager);
         mUserGridView.buildAdapter();
     }
 
@@ -72,8 +79,8 @@
 
 
     @Override
-    public void onUxRestrictionChanged(CarUxRestrictions carUxRestrictions) {
-        applyRestriction(CarUxRestrictionsHelper.isNoSetup(carUxRestrictions));
+    public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
+        applyRestriction(CarUxRestrictionsHelper.isNoSetup(restrictionInfo));
     }
 
     private void applyRestriction(boolean restricted) {
diff --git a/src/com/android/car/settings/users/UserUtils.java b/src/com/android/car/settings/users/UserUtils.java
new file mode 100644
index 0000000..3d26939
--- /dev/null
+++ b/src/com/android/car/settings/users/UserUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+
+import com.android.car.settings.R;
+
+/**
+ * Util class for providing basic, universally needed user-related methods.
+ */
+public class UserUtils {
+    private UserUtils() {
+    }
+
+    /**
+     * Fetches the {@link UserInfo} from UserManager system service for the user ID.
+     *
+     * @param userId ID that corresponds to the returned UserInfo.
+     * @return {@link UserInfo} for the user ID.
+     */
+    public static UserInfo getUserInfo(Context context, int userId) {
+        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        return userManager.getUserInfo(userId);
+    }
+
+    /**
+     * Returns the user name that should be displayed. The caller shouldn't use userInfo.name
+     * directly, because the display name is modified for the current process user.
+     */
+    public static String getUserDisplayName(Context context,
+            CarUserManagerHelper carUserManagerHelper, UserInfo userInfo) {
+        return carUserManagerHelper.isCurrentProcessUser(userInfo) ? context.getString(
+                R.string.current_user_name, userInfo.name) : userInfo.name;
+    }
+
+    /**
+     * Returns whether or not the current user is an admin and whether the user info they are
+     * viewing is of a non-admin.
+     */
+    public static boolean isAdminViewingNonAdmin(CarUserManagerHelper carUserManagerHelper,
+            UserInfo userInfo) {
+        return carUserManagerHelper.isCurrentProcessAdminUser() && !userInfo.isAdmin();
+    }
+
+}
diff --git a/src/com/android/car/settings/users/UsersBasePreferenceController.java b/src/com/android/car/settings/users/UsersBasePreferenceController.java
new file mode 100644
index 0000000..e0f8d4e
--- /dev/null
+++ b/src/com/android/car/settings/users/UsersBasePreferenceController.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.annotation.CallSuper;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Shared business logic between {@link UsersListFragment} and {@link ChooseNewAdminFragment}. */
+public abstract class UsersBasePreferenceController extends PreferenceController<PreferenceGroup> {
+
+    /** Update screen when users list is updated. */
+    private final CarUserManagerHelper.OnUsersUpdateListener mOnUsersUpdateListener =
+            this::refreshUi;
+
+    private UsersPreferenceProvider mPreferenceProvider;
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private List<Preference> mUsersToDisplay = new ArrayList<>();
+
+    public UsersBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        UsersPreferenceProvider.UserClickListener userClickListener = this::userClicked;
+        mPreferenceProvider = new UsersPreferenceProvider(context, mCarUserManagerHelper,
+                userClickListener);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    /**
+     * Ensure that helper is set by the time onCreate is called. Register a listener to refresh
+     * screen on updates.
+     */
+    @Override
+    @CallSuper
+    protected void onCreateInternal() {
+        mCarUserManagerHelper.registerOnUsersUpdateListener(mOnUsersUpdateListener);
+    }
+
+    /** Unregister listener to refresh screen on updates. */
+    @Override
+    @CallSuper
+    protected void onDestroyInternal() {
+        mCarUserManagerHelper.unregisterOnUsersUpdateListener(mOnUsersUpdateListener);
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        List<Preference> newUsers = mPreferenceProvider.createUserList();
+        if (userListsAreDifferent(mUsersToDisplay, newUsers)) {
+            mUsersToDisplay = newUsers;
+            preferenceGroup.removeAll();
+
+            for (Preference preference : mUsersToDisplay) {
+                preferenceGroup.addPreference(preference);
+            }
+        }
+    }
+
+    /** Gets the car user manager helper. */
+    protected CarUserManagerHelper getCarUserManagerHelper() {
+        return mCarUserManagerHelper;
+    }
+
+    /** Handles the user click on a preference for a certain user. */
+    protected abstract void userClicked(UserInfo userInfo);
+
+
+    /** Gets the preference provider to set additional arguments if necessary. */
+    protected UsersPreferenceProvider getPreferenceProvider() {
+        return mPreferenceProvider;
+    }
+
+    private boolean userListsAreDifferent(List<Preference> currentList,
+            List<Preference> newList) {
+        if (currentList.size() != newList.size()) {
+            return true;
+        }
+
+        for (int i = 0; i < currentList.size(); i++) {
+            // Cannot use "compareTo" on preference, since it uses the order attribute to compare.
+            if (preferencesAreDifferent(currentList.get(i), newList.get(i))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean preferencesAreDifferent(Preference lhs, Preference rhs) {
+        return !Objects.equals(lhs.getTitle(), rhs.getTitle())
+                || !Objects.equals(lhs.getSummary(), rhs.getSummary());
+    }
+}
diff --git a/src/com/android/car/settings/users/UsersDialogProvider.java b/src/com/android/car/settings/users/UsersDialogProvider.java
new file mode 100644
index 0000000..cb1fe23
--- /dev/null
+++ b/src/com/android/car/settings/users/UsersDialogProvider.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.users;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+
+/**
+ * Provides common Users-related ConfirmationDialogFragments to ensure consistency
+ */
+public final class UsersDialogProvider {
+
+    /** Argument key to store the user info that the device is trying to make an admin. */
+    static final String KEY_USER_TO_MAKE_ADMIN = "USER_TO_MAKE_ADMIN";
+    /** Argument key to store the user type that the device is trying to remove. */
+    static final String KEY_USER_TYPE = "USER_TYPE";
+    /** {@link KEY_USER_TYPE} value when removing the last admin on the device. */
+    static final String LAST_ADMIN = "LAST_ADMIN";
+    /** {@link KEY_USER_TYPE} value when removing the last user on the device. */
+    static final String LAST_USER = "LAST_USER";
+    /**
+     * Default {@link KEY_USER_TYPE} value when removing a user that is neither {@link LAST_ADMIN}
+     * nor {@link LAST_USER}.
+     */
+    static final String ANY_USER = "ANY_USER";
+
+    private UsersDialogProvider() {
+    }
+
+    /** Gets a confirmation dialog fragment to confirm or reject adding a new user. */
+    public static ConfirmationDialogFragment getConfirmCreateNewUserDialogFragment(Context context,
+            @Nullable ConfirmationDialogFragment.ConfirmListener confirmListener,
+            @Nullable ConfirmationDialogFragment.RejectListener rejectListener) {
+
+        String message = context.getString(R.string.user_add_user_message_setup)
+                .concat(System.lineSeparator())
+                .concat(System.lineSeparator())
+                .concat(context.getString(R.string.user_add_user_message_update));
+
+        ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment.Builder(context)
+                .setTitle(R.string.user_add_user_title)
+                .setMessage(message)
+                .setPositiveButton(android.R.string.ok, confirmListener)
+                .setNegativeButton(android.R.string.cancel, rejectListener)
+                .build();
+
+        return dialogFragment;
+    }
+
+    /** Gets a confirmation dialog fragment to confirm or reject making a user an admin. */
+    public static ConfirmationDialogFragment getConfirmGrantAdminDialogFragment(Context context,
+            @Nullable ConfirmationDialogFragment.ConfirmListener confirmListener,
+            @Nullable ConfirmationDialogFragment.RejectListener rejectListener,
+            UserInfo userToMakeAdmin) {
+
+        String message = context.getString(R.string.grant_admin_permissions_message)
+                .concat(System.lineSeparator())
+                .concat(System.lineSeparator())
+                .concat(context.getString(R.string.action_not_reversible_message));
+
+        ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment.Builder(context)
+                .setTitle(R.string.grant_admin_permissions_title)
+                .setMessage(message)
+                .setPositiveButton(R.string.confirm_grant_admin, confirmListener)
+                .setNegativeButton(android.R.string.cancel, rejectListener)
+                .addArgumentParcelable(KEY_USER_TO_MAKE_ADMIN, userToMakeAdmin)
+                .build();
+
+        return dialogFragment;
+    }
+
+    /**
+     * Gets a confirmation dialog fragment to confirm or reject removing the last user on the
+     * device.
+     */
+    public static ConfirmationDialogFragment getConfirmRemoveLastUserDialogFragment(Context context,
+            @Nullable ConfirmationDialogFragment.ConfirmListener confirmListener,
+            @Nullable ConfirmationDialogFragment.RejectListener rejectListener) {
+
+        String message = context.getString(R.string.delete_last_user_admin_created_message)
+                .concat(System.lineSeparator())
+                .concat(System.lineSeparator())
+                .concat(context.getString(R.string.delete_last_user_system_setup_required_message));
+
+        ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment.Builder(context)
+                .setTitle(R.string.delete_last_user_dialog_title)
+                .setMessage(message)
+                .setPositiveButton(R.string.delete_button, confirmListener)
+                .setNegativeButton(android.R.string.cancel, rejectListener)
+                .addArgumentString(KEY_USER_TYPE, LAST_USER)
+                .build();
+
+        return dialogFragment;
+    }
+
+    /**
+     * Gets a confirmation dialog fragment to confirm or reject removing the last admin user on the
+     * device.
+     */
+    public static ConfirmationDialogFragment getConfirmRemoveLastAdminDialogFragment(
+            Context context,
+            @Nullable ConfirmationDialogFragment.ConfirmListener confirmListener,
+            @Nullable ConfirmationDialogFragment.RejectListener rejectListener) {
+
+        ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment.Builder(context)
+                .setTitle(R.string.choose_new_admin_title)
+                .setMessage(R.string.choose_new_admin_message)
+                .setPositiveButton(R.string.choose_new_admin_label, confirmListener)
+                .setNegativeButton(android.R.string.cancel, rejectListener)
+                .addArgumentString(KEY_USER_TYPE, LAST_ADMIN)
+                .build();
+
+        return dialogFragment;
+    }
+
+    /**
+     * Gets a confirmation dialog fragment to confirm or reject removing a user that is neither the
+     * last admin nor the last user on the device.
+     */
+    public static ConfirmationDialogFragment getConfirmRemoveUserDialogFragment(Context context,
+            @Nullable ConfirmationDialogFragment.ConfirmListener confirmListener,
+            @Nullable ConfirmationDialogFragment.RejectListener rejectListener) {
+
+        ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment.Builder(context)
+                .setTitle(R.string.delete_user_dialog_title)
+                .setMessage(R.string.delete_user_dialog_message)
+                .setPositiveButton(R.string.delete_button, confirmListener)
+                .setNegativeButton(android.R.string.cancel, rejectListener)
+                .addArgumentString(KEY_USER_TYPE, ANY_USER)
+                .build();
+
+        return dialogFragment;
+    }
+}
diff --git a/src/com/android/car/settings/users/UsersEntryPreferenceController.java b/src/com/android/car/settings/users/UsersEntryPreferenceController.java
new file mode 100644
index 0000000..a4f8b8f
--- /dev/null
+++ b/src/com/android/car/settings/users/UsersEntryPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller which determines if the top level entry into User settings should direct to a list
+ * of all users or a user details page based on the current user's admin status.
+ */
+public class UsersEntryPreferenceController extends PreferenceController<Preference> {
+
+    private static final Logger LOG = new Logger(UsersEntryPreferenceController.class);
+
+    private final CarUserManagerHelper mCarUserManagerHelper;
+
+    public UsersEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public boolean handlePreferenceClicked(Preference preference) {
+        if (mCarUserManagerHelper.isCurrentProcessAdminUser()) {
+            // Admins can see a full list of users in Settings.
+            LOG.v("Creating UsersListFragment for admin user.");
+            getFragmentController().launchFragment(new UsersListFragment());
+        } else {
+            // Non-admins can only manage themselves in Settings.
+            LOG.v("Creating UserDetailsFragment for non-admin.");
+            getFragmentController().launchFragment(UserDetailsFragment.newInstance(
+                    mCarUserManagerHelper.getCurrentProcessUserId()));
+        }
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/users/UsersItemProvider.java b/src/com/android/car/settings/users/UsersItemProvider.java
deleted file mode 100644
index e6f5108..0000000
--- a/src/com/android/car/settings/users/UsersItemProvider.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.users;
-
-import android.annotation.Nullable;
-import android.car.user.CarUserManagerHelper;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.graphics.drawable.Drawable;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.TextListItem;
-
-import com.android.car.settings.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implementation of {@link ListItemProvider} for {@link UsersListFragment}.
- * Creates items that represent users on the system.
- */
-class UsersItemProvider extends ListItemProvider {
-    private final List<ListItem> mItems = new ArrayList<>();
-    private final Context mContext;
-    private final UserClickListener mUserClickListener;
-    private final CarUserManagerHelper mCarUserManagerHelper;
-    private final UserIconProvider mUserIconProvider;
-
-    UsersItemProvider(Context context, UserClickListener userClickListener,
-            CarUserManagerHelper userManagerHelper) {
-        mContext = context;
-        mUserClickListener = userClickListener;
-        mCarUserManagerHelper = userManagerHelper;
-        mUserIconProvider = new UserIconProvider(mCarUserManagerHelper);
-        refreshItems();
-    }
-
-    @Override
-    public ListItem get(int position) {
-        return mItems.get(position);
-    }
-
-    @Override
-    public int size() {
-        return mItems.size();
-    }
-
-    /**
-     * Clears and recreates the list of items.
-     */
-    public void refreshItems() {
-        mItems.clear();
-
-        UserInfo currUserInfo = mCarUserManagerHelper.getCurrentForegroundUserInfo();
-
-        // Show current user
-        mItems.add(createUserItem(currUserInfo, /* isCurrentUser= */ true));
-
-        // If the current user is a demo user, don't list any of the other users.
-        if (currUserInfo.isDemo()) {
-            return;
-        }
-
-        // Display other users on the system
-        List<UserInfo> infos = mCarUserManagerHelper.getAllSwitchableUsers();
-        for (UserInfo userInfo : infos) {
-            if (!userInfo.isGuest()) { // Do not show guest users.
-                mItems.add(createUserItem(userInfo, /* isCurrentUser= */ false));
-            }
-        }
-
-        // Display guest session option.
-        if (!currUserInfo.isGuest()) {
-            mItems.add(createGuestItem());
-        }
-    }
-
-    // Creates a line for a user, clicking on it leads to the user details page.
-    private ListItem createUserItem(UserInfo userInfo, boolean isCurrentUser) {
-        TextListItem item = new TextListItem(mContext);
-        item.setPrimaryActionIcon(mUserIconProvider.getUserIcon(userInfo, mContext),
-                /* useLargeIcon= */ false);
-        item.setTitle(getUserItemTitle(userInfo, isCurrentUser));
-        item.setBody(getUserItemSummary(userInfo, isCurrentUser));
-        item.setOnClickListener(view -> mUserClickListener.onUserClicked(userInfo));
-        item.setSupplementalIcon(R.drawable.ic_chevron_right, false);
-        return item;
-    }
-
-    @Nullable
-    private String getUserItemSummary(UserInfo userInfo, boolean isCurrentUser) {
-        if (!userInfo.isInitialized()) {
-            return mContext.getString(R.string.user_summary_not_set_up);
-        }
-        if (userInfo.isAdmin()) {
-            return isCurrentUser ? mContext.getString(R.string.signed_in_admin_user)
-                    : mContext.getString(R.string.user_admin);
-        }
-        // No summary for users who are initialized and not admins.
-        return null;
-    }
-
-    private String getUserItemTitle(UserInfo userInfo, boolean isCurrentUser)  {
-        return isCurrentUser ? mContext.getString(R.string.current_user_name, userInfo.name)
-                : userInfo.name;
-    }
-
-    // Creates a line for a guest session.
-    private ListItem createGuestItem() {
-        Drawable icon = UserIconProvider.scaleUserIcon(mCarUserManagerHelper.getGuestDefaultIcon(),
-                mCarUserManagerHelper, mContext);
-
-        TextListItem item = new TextListItem(mContext);
-        item.setPrimaryActionIcon(icon, /* useLargeIcon= */ false);
-        item.setTitle(mContext.getString(R.string.user_guest));
-
-        item.setOnClickListener(view -> mUserClickListener.onGuestClicked());
-        item.setSupplementalIcon(R.drawable.ic_chevron_right, false);
-        return item;
-    }
-
-    /**
-     * Interface for registering clicks on users.
-     */
-    interface UserClickListener {
-        /**
-         * Invoked when user is clicked.
-         *
-         * @param userInfo User for which the click is registered.
-         */
-        void onUserClicked(UserInfo userInfo);
-
-        /**
-         * Invoked when guest is clicked.
-         */
-        void onGuestClicked();
-    }
-}
diff --git a/src/com/android/car/settings/users/UsersListFragment.java b/src/com/android/car/settings/users/UsersListFragment.java
index 2784aee..160fbb6 100644
--- a/src/com/android/car/settings/users/UsersListFragment.java
+++ b/src/com/android/car/settings/users/UsersListFragment.java
@@ -17,93 +17,99 @@
 package com.android.car.settings.users;
 
 import android.car.drivingstate.CarUxRestrictions;
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
 import android.content.Intent;
-import android.content.pm.UserInfo;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.Button;
 import android.widget.ProgressBar;
 
-import androidx.car.widget.ListItemProvider;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.ListItemSettingsFragment;
+import com.android.car.settings.common.CarUxRestrictionsHelper;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.ErrorDialog;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * Lists all Users available on this device.
  */
-public class UsersListFragment extends ListItemSettingsFragment
-        implements CarUserManagerHelper.OnUsersUpdateListener,
-        UsersItemProvider.UserClickListener,
-        ConfirmCreateNewUserDialog.ConfirmCreateNewUserListener,
+public class UsersListFragment extends SettingsFragment implements
         ConfirmExitRetailModeDialog.ConfirmExitRetailModeListener,
         AddNewUserTask.AddNewUserListener {
     private static final String FACTORY_RESET_PACKAGE_NAME = "android";
     private static final String FACTORY_RESET_REASON = "ExitRetailModeConfirmed";
 
-    private UsersItemProvider mItemProvider;
     private CarUserManagerHelper mCarUserManagerHelper;
 
     private ProgressBar mProgressBar;
     private Button mAddUserButton;
 
     private AsyncTask mAddNewUserTask;
+    /** Indicates that a task is running. */
+    private boolean mIsBusy;
+    private float mOpacityDisabled;
+    private float mOpacityEnabled;
+    private boolean mRestricted;
 
-    public static UsersListFragment newInstance() {
-        UsersListFragment usersListFragment = new UsersListFragment();
-        Bundle bundle = ListItemSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.users_list_title);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        usersListFragment.setArguments(bundle);
-        return usersListFragment;
+    @VisibleForTesting
+    final ConfirmationDialogFragment.ConfirmListener mConfirmListener = arguments -> {
+        mAddNewUserTask = new AddNewUserTask(mCarUserManagerHelper, /* addNewUserListener= */
+                this).execute(getContext().getString(R.string.user_new_user_name));
+        mIsBusy = true;
+        updateUi();
+    };
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.users_list_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mOpacityDisabled = getContext().getResources().getFloat(R.dimen.opacity_disabled);
+        mOpacityEnabled = getContext().getResources().getFloat(R.dimen.opacity_enabled);
+        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
-        mCarUserManagerHelper = new CarUserManagerHelper(getContext());
-        mItemProvider =
-                new UsersItemProvider(getContext(), this, mCarUserManagerHelper);
-
-        // Register to receive changes to the users.
-        mCarUserManagerHelper.registerOnUsersUpdateListener(this);
-
-        // Super class's onActivityCreated need to be called after itemProvider is initialized.
-        // Because getItemProvider is called in there.
         super.onActivityCreated(savedInstanceState);
 
-        mProgressBar = getActivity().findViewById(R.id.progress_bar);
+        mProgressBar = requireActivity().findViewById(R.id.progress_bar);
 
-        mAddUserButton = (Button) getActivity().findViewById(R.id.action_button1);
+        mAddUserButton = getActivity().findViewById(R.id.action_button1);
+        mAddUserButton.setOnClickListener(v -> {
+            if (mRestricted) {
+                showBlockingMessage();
+            } else {
+                handleAddUserClicked();
+            }
+        });
         if (mCarUserManagerHelper.isCurrentProcessDemoUser()) {
-            // If the user is a demo user, show a dialog asking if they want to exit retail/demo
-            // mode
             mAddUserButton.setText(R.string.exit_retail_button_text);
-            mAddUserButton.setOnClickListener(v -> {
-                ConfirmExitRetailModeDialog dialog = new ConfirmExitRetailModeDialog();
-                dialog.setConfirmExitRetailModeListener(this);
-                dialog.show(this);
-            });
         } else if (mCarUserManagerHelper.canCurrentProcessAddUsers()) {
-            // Only add the add user button if the current user is allowed to add a user.
             mAddUserButton.setText(R.string.user_add_user_menu);
-            mAddUserButton.setOnClickListener(v -> {
-                ConfirmCreateNewUserDialog dialog =
-                        new ConfirmCreateNewUserDialog();
-                dialog.setConfirmCreateNewUserListener(this);
-                dialog.show(this);
-            });
         }
     }
 
     @Override
-    public void onCreateNewUserConfirmed() {
-        mAddUserButton.setEnabled(false);
-        mProgressBar.setVisibility(View.VISIBLE);
-        mAddNewUserTask =
-                new AddNewUserTask(mCarUserManagerHelper, /* addNewUserListener= */ this)
-                        .execute(getContext().getString(R.string.user_new_user_name));
+    public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
+        mRestricted = CarUxRestrictionsHelper.isNoSetup(restrictionInfo);
+        mAddUserButton.setAlpha(mRestricted ? mOpacityDisabled : mOpacityEnabled);
     }
 
     /**
@@ -123,58 +129,70 @@
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+        updateUi();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mProgressBar.setVisibility(View.GONE);
+    }
+
+    @Override
     public void onDestroy() {
         super.onDestroy();
 
         if (mAddNewUserTask != null) {
             mAddNewUserTask.cancel(/* mayInterruptIfRunning= */ false);
         }
-
-        mCarUserManagerHelper.unregisterOnUsersUpdateListener(this);
     }
 
     @Override
-    public void onUsersUpdate() {
-        refreshListItems();
+    public void onUserAddedSuccess() {
+        mIsBusy = false;
+        updateUi();
     }
 
     @Override
-    public void onUserClicked(UserInfo userInfo) {
-        if (mCarUserManagerHelper.isForegroundUser(userInfo)) {
-            // If it's the foreground user, launch fragment that allows them to edit their name.
-            getFragmentController().launchFragment(EditUsernameFragment.newInstance(userInfo));
-        } else {
-            // If it's another user, launch fragment that displays their information
-            getFragmentController().launchFragment(UserDetailsFragment.newInstance(userInfo));
+    public void onUserAddedFailure() {
+        mIsBusy = false;
+        updateUi();
+        // Display failure dialog.
+        ErrorDialog.show(this, R.string.add_user_error_title);
+    }
+
+    private void updateUi() {
+        mAddUserButton.setEnabled(!mIsBusy);
+        mProgressBar.setVisibility(mIsBusy ? View.VISIBLE : View.GONE);
+    }
+
+    private void handleAddUserClicked() {
+        // If the user is a demo user, show a dialog asking if they want to exit retail/demo mode.
+        if (mCarUserManagerHelper.isCurrentProcessDemoUser()) {
+            ConfirmExitRetailModeDialog dialog = new ConfirmExitRetailModeDialog();
+            dialog.setConfirmExitRetailModeListener(this);
+            dialog.show(this);
+            return;
         }
-    }
 
-    /**
-     * User list fragment is distraction optimized, so is allowed at all times.
-     */
-    @Override
-    public boolean canBeShown(CarUxRestrictions carUxRestrictions) {
-        return true;
-    }
+        // If no more users can be added because the maximum allowed number is reached, let the user
+        // know.
+        if (mCarUserManagerHelper.isUserLimitReached()) {
+            MaxUsersLimitReachedDialog dialog = new MaxUsersLimitReachedDialog(
+                    mCarUserManagerHelper.getMaxSupportedRealUsers());
+            dialog.show(this);
+            return;
+        }
 
-    @Override
-    public void onGuestClicked() {
-        getFragmentController().launchFragment(GuestFragment.newInstance());
-    }
+        // Only add the add user button if the current user is allowed to add a user.
+        if (mCarUserManagerHelper.canCurrentProcessAddUsers()) {
+            ConfirmationDialogFragment dialogFragment =
+                    UsersDialogProvider.getConfirmCreateNewUserDialogFragment(getContext(),
+                            mConfirmListener, null);
 
-    @Override
-    public ListItemProvider getItemProvider() {
-        return mItemProvider;
-    }
-
-    private void refreshListItems() {
-        mItemProvider.refreshItems();
-        refreshList();
-    }
-
-    @Override
-    public void onUserAdded() {
-        mAddUserButton.setEnabled(true);
-        mProgressBar.setVisibility(View.GONE);
+            dialogFragment.show(getFragmentManager(), ConfirmationDialogFragment.TAG);
+        }
     }
 }
diff --git a/src/com/android/car/settings/users/UsersListPreferenceController.java b/src/com/android/car/settings/users/UsersListPreferenceController.java
new file mode 100644
index 0000000..793e759
--- /dev/null
+++ b/src/com/android/car/settings/users/UsersListPreferenceController.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Business logic for populating the users for the users list settings. */
+public class UsersListPreferenceController extends UsersBasePreferenceController {
+
+    public UsersListPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected void userClicked(UserInfo userInfo) {
+        if (UserUtils.isAdminViewingNonAdmin(getCarUserManagerHelper(), userInfo)) {
+            // Admin viewing non admin.
+            getFragmentController().launchFragment(
+                    UserDetailsPermissionsFragment.newInstance(userInfo.id));
+        } else {
+            getFragmentController().launchFragment(UserDetailsFragment.newInstance(userInfo.id));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/users/UsersPreferenceProvider.java b/src/com/android/car/settings/users/UsersPreferenceProvider.java
new file mode 100644
index 0000000..a34f99b
--- /dev/null
+++ b/src/com/android/car/settings/users/UsersPreferenceProvider.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Constructs the preferences to be displayed in {@link UsersListFragment} and
+ * {@link ChooseNewAdminFragment}.
+ */
+public class UsersPreferenceProvider {
+
+    /**
+     * Interface for registering clicks on users.
+     */
+    public interface UserClickListener {
+        /**
+         * Invoked when user is clicked.
+         *
+         * @param userInfo User for which the click is registered.
+         */
+        void onUserClicked(UserInfo userInfo);
+    }
+
+    private final Context mContext;
+    private final CarUserManagerHelper mCarUserManagerHelper;
+    private final UserClickListener mUserPreferenceClickListener;
+    private boolean mIncludeCurrentUser;
+    private boolean mIncludeGuest;
+
+    public UsersPreferenceProvider(Context context, CarUserManagerHelper carUserManagerHelper,
+            UserClickListener listener) {
+        mContext = context;
+        mCarUserManagerHelper = carUserManagerHelper;
+        mUserPreferenceClickListener = listener;
+        mIncludeCurrentUser = true;
+        mIncludeGuest = true;
+    }
+
+    /**
+     * Sets whether createUserList should include an entry for the current user. Default is
+     * {@code true}.
+     */
+    public void setIncludeCurrentUser(boolean includeCurrentUser) {
+        mIncludeCurrentUser = includeCurrentUser;
+    }
+
+    /**
+     * Sets whether createUserList should include an entry for the guest profile. Default is
+     * {@code true}.
+     */
+    public void setIncludeGuest(boolean includeGuest) {
+        mIncludeGuest = includeGuest;
+    }
+
+    /**
+     * Creates the list of users (as preferences). The first user will be the current user (if
+     * requested) and the last user will be the guest user (if requested). Otherwise, the list is
+     * populated with all of the remaining switchable users.
+     */
+    public List<Preference> createUserList() {
+        List<Preference> users = new ArrayList<>();
+        UserInfo currUserInfo = mCarUserManagerHelper.getCurrentProcessUserInfo();
+
+        // Show current user
+        if (mIncludeCurrentUser) {
+            users.add(createUserPreference(currUserInfo));
+        }
+
+        // Display other users on the system
+        List<UserInfo> infos = mCarUserManagerHelper.getAllSwitchableUsers();
+        for (UserInfo userInfo : infos) {
+            if (!userInfo.isGuest()) { // Do not show guest users.
+                users.add(createUserPreference(userInfo));
+            }
+        }
+
+        // Display guest session option.
+        if (mIncludeGuest) {
+            users.add(createGuestUserPreference());
+        }
+
+        return users;
+    }
+
+    private Preference createUserPreference(UserInfo userInfo) {
+        Preference preference = new Preference(mContext);
+        preference.setIcon(
+                new UserIconProvider(mCarUserManagerHelper).getUserIcon(userInfo, mContext));
+        preference.setTitle(
+                UserUtils.getUserDisplayName(mContext, mCarUserManagerHelper, userInfo));
+
+        if (!userInfo.isInitialized()) {
+            preference.setSummary(R.string.user_summary_not_set_up);
+        }
+        if (userInfo.isAdmin()) {
+            preference.setSummary(
+                    isCurrentUser(userInfo) ? R.string.signed_in_admin_user : R.string.user_admin);
+        }
+
+        preference.setOnPreferenceClickListener(pref -> {
+            if (mUserPreferenceClickListener == null) {
+                return false;
+            }
+            mUserPreferenceClickListener.onUserClicked(userInfo);
+            return true;
+        });
+
+        return preference;
+    }
+
+    private Preference createGuestUserPreference() {
+        Preference preference = new Preference(mContext);
+        preference.setIcon(
+                new UserIconProvider(mCarUserManagerHelper).getDefaultGuestIcon(mContext));
+        preference.setTitle(R.string.user_guest);
+        return preference;
+    }
+
+    private boolean isCurrentUser(UserInfo userInfo) {
+        return mCarUserManagerHelper.isCurrentProcessUser(userInfo);
+    }
+}
diff --git a/src/com/android/car/settings/wifi/AccessPointListAdapter.java b/src/com/android/car/settings/wifi/AccessPointListAdapter.java
deleted file mode 100644
index ea66e0f..0000000
--- a/src/com/android/car/settings/wifi/AccessPointListAdapter.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.settings.wifi;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.net.wifi.WifiManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.car.widget.PagedListView;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.settingslib.wifi.AccessPoint;
-
-import java.util.List;
-
-/**
- * Renders {@link AccessPoint} to a view to be displayed as a row in a list.
- */
-public class AccessPointListAdapter
-        extends RecyclerView.Adapter<AccessPointListAdapter.ViewHolder>
-        implements PagedListView.ItemCap {
-    private static final int NETWORK_ROW_TYPE = 1;
-    private static final int ADD_NETWORK_ROW_TYPE = 2;
-
-    private static final int[] STATE_SECURED = {
-            com.android.settingslib.R.attr.state_encrypted
-    };
-    private static final int[] STATE_NONE = {};
-    private static int[] wifi_signal_attributes = {com.android.settingslib.R.attr.wifi_signal};
-
-    private final StateListDrawable mWifiSld;
-    private final Context mContext;
-    private final BaseFragment.FragmentController mFragmentController;
-    private final CarWifiManager mCarWifiManager;
-    private final WifiManager.ActionListener mConnectionListener;
-    private boolean mShowAddNetworkRow;
-
-    private List<AccessPoint> mAccessPoints;
-
-    public AccessPointListAdapter(
-            @NonNull Context context,
-            CarWifiManager carWifiManager,
-            @NonNull List<AccessPoint> accesssPoints,
-            BaseFragment.FragmentController fragmentController) {
-        mContext = context;
-        mFragmentController = fragmentController;
-        mCarWifiManager = carWifiManager;
-        mAccessPoints = accesssPoints;
-        mWifiSld = (StateListDrawable) context.getTheme()
-                .obtainStyledAttributes(wifi_signal_attributes).getDrawable(0);
-
-        mConnectionListener = new WifiManager.ActionListener() {
-            @Override
-            public void onSuccess() {
-            }
-            @Override
-            public void onFailure(int reason) {
-                Toast.makeText(mContext,
-                        R.string.wifi_failed_connect_message,
-                        Toast.LENGTH_SHORT).show();
-            }
-        };
-    }
-
-    /**
-     * Toggles the row that links to add a new network.
-     */
-    public AccessPointListAdapter showAddNetworkRow(boolean show) {
-        mShowAddNetworkRow = show;
-        return this;
-    }
-
-    public void updateAccessPoints(@NonNull List<AccessPoint> accesssPoints) {
-        mAccessPoints = accesssPoints;
-        notifyDataSetChanged();
-    }
-
-    public boolean isEmpty() {
-        return mAccessPoints.isEmpty();
-    }
-
-    public class ViewHolder extends RecyclerView.ViewHolder {
-        private final ImageView mIcon;
-        private final ImageView mRightChevron;
-        private final TextView mWifiName;
-        private final TextView mWifiDesc;
-
-        public ViewHolder(View view) {
-            super(view);
-            mWifiName = (TextView) view.findViewById(R.id.title);
-            mWifiDesc = (TextView) view.findViewById(R.id.desc);
-            mIcon = (ImageView) view.findViewById(R.id.icon);
-            mRightChevron = (ImageView) view.findViewById(R.id.right_chevron);
-        }
-    }
-
-    private class AccessPointClickListener implements OnClickListener {
-        private final AccessPoint mAccessPoint;
-
-        public AccessPointClickListener(AccessPoint accessPoint) {
-            mAccessPoint = accessPoint;
-        }
-
-        @Override
-        public void onClick(View v) {
-            // for new open unsecuried wifi network, connect to it right away
-            if (mAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
-                    !mAccessPoint.isSaved() && !mAccessPoint.isActive()) {
-                mCarWifiManager.connectToPublicWifi(mAccessPoint, mConnectionListener);
-            } else if (mAccessPoint.isSaved()) {
-                mFragmentController.launchFragment(WifiDetailFragment.getInstance(mAccessPoint));
-            } else {
-                mFragmentController.launchFragment(AddWifiFragment.getInstance(mAccessPoint));
-            }
-        }
-    };
-
-    @Override
-    public int getItemViewType(int position) {
-        // the last row is the add device row
-        if (position == mAccessPoints.size()) {
-            return ADD_NETWORK_ROW_TYPE;
-        }
-        return NETWORK_ROW_TYPE;
-    }
-
-    @Override
-    public AccessPointListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
-            int viewType) {
-        ViewHolder viewHolder = new ViewHolder(LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.icon_text_line_item, parent, false));
-        if (viewType == ADD_NETWORK_ROW_TYPE) {
-            viewHolder.mIcon.setImageResource(R.drawable.ic_add);
-            viewHolder.mWifiDesc.setVisibility(View.GONE);
-            viewHolder.mWifiName.setText(R.string.wifi_setup_add_network);
-            viewHolder.itemView.setOnClickListener(v -> {
-                mFragmentController.launchFragment(AddWifiFragment.getInstance(null));
-            });
-        }
-        return viewHolder;
-    }
-
-    @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        // for the last row, it's the "add network button", no more binding needed.
-        if (position >= mAccessPoints.size()) {
-            return;
-        }
-        AccessPoint accessPoint = mAccessPoints.get(position);
-        holder.itemView.setOnClickListener(new AccessPointClickListener(accessPoint));
-        holder.mWifiName.setText(accessPoint.getConfigName());
-        holder.mIcon.setImageDrawable(getIcon(accessPoint));
-        String summary = accessPoint.getSummary();
-        if (summary != null && !summary.isEmpty()) {
-            holder.mWifiDesc.setText(summary);
-            holder.mWifiDesc.setVisibility(View.VISIBLE);
-        } else {
-            holder.mWifiDesc.setVisibility(View.GONE);
-        }
-        if (accessPoint.getSecurity() == accessPoint.SECURITY_NONE &&
-                !accessPoint.isSaved() && !accessPoint.isActive()) {
-            holder.mRightChevron.setVisibility(View.GONE);
-        } else {
-            holder.mRightChevron.setVisibility(View.VISIBLE);
-        }
-    }
-
-    @Override
-    public int getItemCount() {
-        // number of rows include one per device and a row for add network.
-        return mShowAddNetworkRow ? mAccessPoints.size() + 1 : mAccessPoints.size();
-    }
-
-    @Override
-    public void setMaxItems(int maxItems) {
-        // no limit in this list.
-    }
-
-    private Drawable getIcon(AccessPoint accessPoint) {
-        mWifiSld.setState((accessPoint.getSecurity() != AccessPoint.SECURITY_NONE)
-                ? STATE_SECURED
-                : STATE_NONE);
-        Drawable drawable = mWifiSld.getCurrent();
-        drawable.setLevel(accessPoint.getLevel());
-        drawable.invalidateSelf();
-        return drawable;
-    }
-}
diff --git a/src/com/android/car/settings/wifi/AccessPointListPreferenceController.java b/src/com/android/car/settings/wifi/AccessPointListPreferenceController.java
new file mode 100644
index 0000000..10c7f84
--- /dev/null
+++ b/src/com/android/car/settings/wifi/AccessPointListPreferenceController.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.CarUxRestrictionsHelper;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.wifi.details.WifiDetailsFragment;
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Renders a list of {@link AccessPoint} as a list of preference.
+ */
+public class AccessPointListPreferenceController extends
+        WifiBasePreferenceController<PreferenceGroup> implements
+        Preference.OnPreferenceClickListener,
+        Preference.OnPreferenceChangeListener,
+        CarUxRestrictionsManager.OnUxRestrictionsChangedListener {
+    private static final Logger LOG = new Logger(AccessPointListPreferenceController.class);
+    private List<AccessPoint> mAccessPoints = new ArrayList<>();
+
+    private final WifiManager.ActionListener mConnectionListener =
+            new WifiManager.ActionListener() {
+                @Override
+                public void onSuccess() {
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    Toast.makeText(getContext(),
+                            R.string.wifi_failed_connect_message,
+                            Toast.LENGTH_SHORT).show();
+                }
+            };
+
+    public AccessPointListPreferenceController(@NonNull Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preferenceGroup) {
+        if (getCarWifiManager() == null) {
+            return;
+        }
+        mAccessPoints = CarUxRestrictionsHelper.isNoSetup(getUxRestrictions())
+                ? getCarWifiManager().getSavedAccessPoints()
+                : getCarWifiManager().getAllAccessPoints();
+        LOG.d("showing accessPoints: " + mAccessPoints.size());
+
+        preferenceGroup.setVisible(!mAccessPoints.isEmpty());
+        preferenceGroup.removeAll();
+        for (AccessPoint accessPoint : mAccessPoints) {
+            preferenceGroup.addPreference(createAccessPointPreference(accessPoint));
+        }
+    }
+
+    @Override
+    protected void onApplyUxRestrictions(CarUxRestrictions uxRestrictions) {
+        // Since the list dynamically changes based on the ux restrictions, we enable this fragment
+        // regardless of the restriction. Intentional no-op.
+    }
+
+    @Override
+    public void onAccessPointsChanged() {
+        refreshUi();
+    }
+
+    @Override
+    public void onWifiStateChanged(int state) {
+        if (state == WifiManager.WIFI_STATE_ENABLED) {
+            refreshUi();
+        }
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        AccessPoint accessPoint = ((AccessPointPreference) preference).getAccessPoint();
+        // For new open unsecuried wifi network, connect to it right away.
+        if (accessPoint.getSecurity() == AccessPoint.SECURITY_NONE
+                && !accessPoint.isSaved() && !accessPoint.isActive()) {
+            getCarWifiManager().connectToPublicWifi(accessPoint, mConnectionListener);
+        } else if (accessPoint.isActive()) {
+            getFragmentController().launchFragment(WifiDetailsFragment.getInstance(accessPoint));
+        } else if (accessPoint.isSaved()) {
+            getCarWifiManager().connectToSavedWifi(accessPoint, mConnectionListener);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        AccessPoint accessPoint = ((AccessPointPreference) preference).getAccessPoint();
+        WifiUtil.connectToAccessPoint(getContext(), accessPoint.getSsid().toString(),
+                accessPoint.getSecurity(), newValue.toString(), /* hidden= */ false);
+        return true;
+    }
+
+    private AccessPointPreference createAccessPointPreference(AccessPoint accessPoint) {
+        LOG.d("Adding preference for " + WifiUtil.getKey(accessPoint));
+        AccessPointPreference accessPointPreference = new AccessPointPreference(getContext(),
+                accessPoint);
+        accessPointPreference.setKey(accessPoint.getKey());
+        accessPointPreference.setTitle(accessPoint.getConfigName());
+        accessPointPreference.setDialogTitle(accessPoint.getConfigName());
+        accessPointPreference.setSummary(accessPoint.getSummary());
+        accessPointPreference.setOnPreferenceClickListener(this);
+        accessPointPreference.setOnPreferenceChangeListener(this);
+        return accessPointPreference;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/AccessPointPreference.java b/src/com/android/car/settings/wifi/AccessPointPreference.java
new file mode 100644
index 0000000..cddafee
--- /dev/null
+++ b/src/com/android/car/settings/wifi/AccessPointPreference.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PasswordEditTextPreference;
+import com.android.settingslib.wifi.AccessPoint;
+
+/** Renders a {@link AccessPoint} as a preference. */
+public class AccessPointPreference extends PasswordEditTextPreference {
+    private static final Logger LOG = new Logger(AccessPointPreference.class);
+    private static final int[] STATE_SECURED = {
+            com.android.settingslib.R.attr.state_encrypted
+    };
+    private static final int[] STATE_NONE = {};
+    private static int[] sWifiSignalAttributes = {com.android.settingslib.R.attr.wifi_signal};
+
+    private final StateListDrawable mWifiSld;
+    private final AccessPoint mAccessPoint;
+
+    public AccessPointPreference(
+            Context context,
+            AccessPoint accessPoint) {
+        super(context);
+        mWifiSld = (StateListDrawable) context.getTheme()
+                .obtainStyledAttributes(sWifiSignalAttributes).getDrawable(0);
+        mAccessPoint = accessPoint;
+        LOG.d("creating preference for ap: " + mAccessPoint);
+        setIcon(getAccessPointIcon());
+    }
+
+    /**
+     * Returns the {@link AccessPoint}.
+     */
+    public AccessPoint getAccessPoint() {
+        return mAccessPoint;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        setIcon(getAccessPointIcon());
+    }
+
+    @Override
+    protected void onClick() {
+        if (shouldShowPasswordDialog()) {
+            super.onClick();
+        }
+    }
+
+    /**
+     * Show password dialog for one of the following conditions:
+     * 1. AP with some security but is not saved and not active
+     * 2. AP that has been saved, but not enabled due to wrong password.
+     */
+    private boolean shouldShowPasswordDialog() {
+        return mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE && (!mAccessPoint.isSaved()
+                || isAccessPointDisabledByWrongPassword(mAccessPoint));
+    }
+
+    private Drawable getAccessPointIcon() {
+        if (mWifiSld == null) {
+            LOG.w("wifiSld is null.");
+            return null;
+        }
+        mWifiSld.setState(
+                (mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE)
+                        ? STATE_SECURED
+                        : STATE_NONE);
+        Drawable drawable = mWifiSld.getCurrent();
+        drawable.setLevel(mAccessPoint.getLevel());
+        return drawable;
+    }
+
+    private boolean isAccessPointDisabledByWrongPassword(AccessPoint accessPoint) {
+        WifiConfiguration config = accessPoint.getConfig();
+        if (config == null) {
+            return false;
+        }
+        WifiConfiguration.NetworkSelectionStatus networkStatus =
+                config.getNetworkSelectionStatus();
+        if (networkStatus == null || networkStatus.isNetworkEnabled()) {
+            return false;
+        }
+        return networkStatus.getNetworkSelectionDisableReason()
+                == WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/AddWifiFragment.java b/src/com/android/car/settings/wifi/AddWifiFragment.java
index 3e30f8f..000a53a 100644
--- a/src/com/android/car/settings/wifi/AddWifiFragment.java
+++ b/src/com/android/car/settings/wifi/AddWifiFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -13,88 +13,85 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.car.settings.wifi;
 
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.view.View;
-import android.widget.AdapterView;
+import android.text.TextUtils;
 import android.widget.Button;
-import android.widget.Toast;
 
-import com.android.car.list.EditTextLineItem;
-import com.android.car.list.PasswordLineItem;
-import com.android.car.list.SpinnerLineItem;
-import com.android.car.list.TypedPagedListAdapter;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
 import com.android.car.settings.R;
-import com.android.car.settings.common.ListSettingsFragment;
 import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
 import com.android.settingslib.wifi.AccessPoint;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
 /**
- * Adds a wifi network, the network can be public or private. If ADD_NETWORK_MODE is not specified
- * in the intent, then it needs to contain AccessPoint information, which is be use that to
- * render UI, e.g. show SSID etc.
+ * Adds a hidden wifi network. The connect button on the fragment is only used for unsecure hidden
+ * networks. The remaining security types can be connected via pressing connect on the password
+ * dialog.
  */
-public class AddWifiFragment extends ListSettingsFragment implements
-        AdapterView.OnItemSelectedListener {
-    public static final String EXTRA_AP_STATE = "extra_ap_state";
-
+public class AddWifiFragment extends SettingsFragment {
     private static final Logger LOG = new Logger(AddWifiFragment.class);
-    private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9A-F]+$");
-    private static final Pattern VALID_SSID_PATTERN =
-            Pattern.compile("^[A-Za-z]+[\\w\\-\\:\\.]*$");
-    @Nullable
-    private AccessPoint mAccessPoint;
-    @Nullable
-    private SpinnerLineItem<AccessPointSecurity> mSpinnerLineItem;
-    private WifiManager mWifiManager;
-    private Button mAddWifiButton;
-    private final WifiManager.ActionListener mConnectionListener =
-            new WifiManager.ActionListener() {
-                @Override
-                public void onSuccess() {
-                }
+    private static final String KEY_NETWORK_NAME = "network_name";
+    private static final String KEY_SECURITY_TYPE = "security_type";
 
-                @Override
-                public void onFailure(int reason) {
-                    Toast.makeText(getContext(),
-                            R.string.wifi_failed_connect_message,
-                            Toast.LENGTH_SHORT).show();
-                }
-            };
-    private EditTextLineItem mWifiNameInput;
-    private EditTextLineItem mWifiPasswordInput;
-
-    private int mSelectedPosition = AccessPointSecurity.SECURITY_NONE_POSITION;
-
-    public static AddWifiFragment getInstance(AccessPoint accessPoint) {
-        AddWifiFragment addWifiFragment = new AddWifiFragment();
-        Bundle bundle = ListSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.wifi_setup_add_network);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        Bundle accessPointState = new Bundle();
-        if (accessPoint != null) {
-            accessPoint.saveWifiState(accessPointState);
-            bundle.putBundle(EXTRA_AP_STATE, accessPointState);
+    private final BroadcastReceiver mNameChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mNetworkName = intent.getStringExtra(
+                    NetworkNamePreferenceController.KEY_NETWORK_NAME);
+            setButtonEnabledState();
         }
-        addWifiFragment.setArguments(bundle);
-        return addWifiFragment;
+    };
+
+    private final BroadcastReceiver mSecurityChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mSecurityType = intent.getIntExtra(
+                    NetworkSecurityPreferenceController.KEY_SECURITY_TYPE,
+                    AccessPoint.SECURITY_NONE);
+            setButtonEnabledState();
+        }
+    };
+
+    private Button mAddWifiButton;
+    private String mNetworkName;
+    private int mSecurityType = AccessPoint.SECURITY_NONE;
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.add_wifi_fragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        if (getArguments().keySet().contains(EXTRA_AP_STATE)) {
-            mAccessPoint = new AccessPoint(getContext(), getArguments().getBundle(EXTRA_AP_STATE));
+        if (savedInstanceState != null) {
+            mNetworkName = savedInstanceState.getString(KEY_NETWORK_NAME);
+            mSecurityType = savedInstanceState.getInt(KEY_SECURITY_TYPE, AccessPoint.SECURITY_NONE);
         }
-        mWifiManager = getContext().getSystemService(WifiManager.class);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString(KEY_NETWORK_NAME, mNetworkName);
+        outState.putInt(KEY_SECURITY_TYPE, mSecurityType);
     }
 
     @Override
@@ -103,126 +100,40 @@
 
         mAddWifiButton = getActivity().findViewById(R.id.action_button1);
         mAddWifiButton.setText(R.string.wifi_setup_connect);
+        setButtonEnabledState();
+
+        // This only needs to handle hidden/unsecure networks.
         mAddWifiButton.setOnClickListener(v -> {
-            connectToAccessPoint();
-            getFragmentController().goBack();
+            int netId = WifiUtil.connectToAccessPoint(getContext(), mNetworkName,
+                    AccessPoint.SECURITY_NONE, /* password= */ null, /* hidden= */ true);
+
+            LOG.d("connected to netId: " + netId);
+            if (netId != WifiUtil.INVALID_NET_ID) {
+                goBack();
+            }
         });
-        mAddWifiButton.setEnabled(mAccessPoint != null);
     }
 
     @Override
-    public ArrayList<TypedPagedListAdapter.LineItem> getLineItems() {
-        ArrayList<TypedPagedListAdapter.LineItem> lineItems = new ArrayList<>();
-        if (mAccessPoint != null) {
-            mWifiNameInput = new EditTextLineItem(
-                    getContext().getText(R.string.wifi_ssid), mAccessPoint.getSsid());
-            mWifiNameInput.setTextType(EditTextLineItem.TextType.NONE);
-        } else {
-            mWifiNameInput = new EditTextLineItem(
-                    getContext().getText(R.string.wifi_ssid));
-            mWifiNameInput.setTextType(EditTextLineItem.TextType.TEXT);
-            mWifiNameInput.setTextChangeListener(s ->
-                    mAddWifiButton.setEnabled(VALID_SSID_PATTERN.matcher(s).matches()));
-        }
-        lineItems.add(mWifiNameInput);
-
-        if (mAccessPoint == null) {
-            List<AccessPointSecurity> securities =
-                    AccessPointSecurity.getSecurityTypes(getContext());
-            mSpinnerLineItem = new SpinnerLineItem<>(
-                    getContext(),
-                    this,
-                    securities,
-                    getContext().getText(R.string.wifi_security),
-                    mSelectedPosition);
-            lineItems.add(mSpinnerLineItem);
-        }
-
-        if (mAccessPoint != null
-                || mSelectedPosition != AccessPointSecurity.SECURITY_NONE_POSITION) {
-            mWifiPasswordInput = new PasswordLineItem(getContext().getText(R.string.wifi_password));
-            lineItems.add(mWifiPasswordInput);
-        }
-        return lineItems;
+    public void onStart() {
+        super.onStart();
+        LocalBroadcastManager.getInstance(getContext()).registerReceiver(mNameChangeReceiver,
+                new IntentFilter(NetworkNamePreferenceController.ACTION_NAME_CHANGE));
+        LocalBroadcastManager.getInstance(getContext()).registerReceiver(mSecurityChangeReceiver,
+                new IntentFilter(NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE));
     }
 
     @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        if (position == mSelectedPosition) {
-            return;
-        }
-        mSelectedPosition = position;
-        mPagedListAdapter.setList(getLineItems());
+    public void onStop() {
+        super.onStop();
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mNameChangeReceiver);
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mSecurityChangeReceiver);
     }
 
-    @Override
-    public void onNothingSelected(AdapterView<?> parent) {
-    }
-
-    private void connectToAccessPoint() {
-        WifiConfiguration wifiConfig = new WifiConfiguration();
-        wifiConfig.SSID = String.format("\"%s\"", getSsId());
-        wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-        wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
-        wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-        wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
-        wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
-        wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
-        int security;
-        if (mAccessPoint == null) {
-            security = mSpinnerLineItem.getItem(mSelectedPosition).getSecurityType();
-            wifiConfig.hiddenSSID = true;
-        } else {
-            security = mAccessPoint.getSecurity();
-        }
-        switch (security) {
-            case AccessPoint.SECURITY_NONE:
-                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-                wifiConfig.allowedAuthAlgorithms.clear();
-                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
-                break;
-            case AccessPoint.SECURITY_WEP:
-                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-                wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
-                wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
-                String password = mWifiPasswordInput.getInput();
-                wifiConfig.wepKeys[0] = isHexString(password) ? password
-                        : "\"" + password + "\"";
-                wifiConfig.wepTxKeyIndex = 0;
-                break;
-            case AccessPoint.SECURITY_PSK:
-            case AccessPoint.SECURITY_EAP:
-                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
-                wifiConfig.preSharedKey = String.format(
-                        "\"%s\"", mWifiPasswordInput.getInput());
-                break;
-            default:
-                LOG.w("invalid security type: " + security);
-                break;
-        }
-        int netId = mWifiManager.addNetwork(wifiConfig);
-        if (netId == -1) {
-            Toast.makeText(getContext(),
-                    R.string.wifi_failed_connect_message,
-                    Toast.LENGTH_SHORT).show();
-        } else {
-            mWifiManager.enableNetwork(netId, true);
-        }
-    }
-
-    private boolean isHexString(String password) {
-        return HEX_PATTERN.matcher(password).matches();
-    }
-
-    // TODO: handle null case, show warning message etc.
-    private String getSsId() {
-        if (mAccessPoint == null) {
-            return mWifiNameInput.getInput();
-        } else {
-            return mAccessPoint.getSsid().toString();
+    private void setButtonEnabledState() {
+        if (mAddWifiButton != null) {
+            mAddWifiButton.setEnabled(
+                    !TextUtils.isEmpty(mNetworkName) && mSecurityType == AccessPoint.SECURITY_NONE);
         }
     }
 }
diff --git a/src/com/android/car/settings/wifi/AddWifiPreferenceController.java b/src/com/android/car/settings/wifi/AddWifiPreferenceController.java
new file mode 100644
index 0000000..08d2910
--- /dev/null
+++ b/src/com/android/car/settings/wifi/AddWifiPreferenceController.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Controls preference for adding wifi.
+ */
+public class AddWifiPreferenceController extends WifiBasePreferenceController<Preference> {
+
+    public AddWifiPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public void onWifiStateChanged(int state) {
+        switch (state) {
+            case WifiManager.WIFI_STATE_DISABLED:
+                getPreference().setVisible(false);
+                break;
+            default:
+                getPreference().setVisible(true);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/car/settings/wifi/CarWifiManager.java b/src/com/android/car/settings/wifi/CarWifiManager.java
index 7530e3f..6f4a9f8 100644
--- a/src/com/android/car/settings/wifi/CarWifiManager.java
+++ b/src/com/android/car/settings/wifi/CarWifiManager.java
@@ -15,11 +15,13 @@
  */
 package com.android.car.settings.wifi;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
-import android.support.annotation.UiThread;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.settingslib.wifi.WifiTracker;
@@ -32,11 +34,12 @@
  */
 public class CarWifiManager implements WifiTracker.WifiListener {
     private final Context mContext;
-    private Listener mListener;
+    private final List<Listener> mListeners = new ArrayList<>();
     private boolean mStarted;
 
     private WifiTracker mWifiTracker;
     private WifiManager mWifiManager;
+
     public interface Listener {
         /**
          * Something about wifi setting changed.
@@ -59,14 +62,27 @@
         void onWifiStateChanged(int state);
     }
 
-    public CarWifiManager(Context context, Listener listener) {
+    public CarWifiManager(Context context) {
         mContext = context;
-        mListener = listener;
-        mWifiManager = (WifiManager) mContext.getSystemService(WifiManager.class);
+        mWifiManager = mContext.getSystemService(WifiManager.class);
         mWifiTracker = new WifiTracker(context, this, true, true);
     }
 
     /**
+     * Adds {@link Listener}.
+     */
+    public boolean addListener(Listener listener) {
+        return mListeners.add(listener);
+    }
+
+    /**
+     * Removes {@link Listener}.
+     */
+    public boolean removeListener(Listener listener) {
+        return mListeners.remove(listener);
+    }
+
+    /**
      * Starts {@link CarWifiManager}.
      * This should be called only from main thread.
      */
@@ -141,26 +157,84 @@
         return null;
     }
 
+    /**
+     * Returns {@code true} if Wifi is enabled
+     */
     public boolean isWifiEnabled() {
         return mWifiManager.isWifiEnabled();
     }
 
+    /**
+     * Returns {@code true} if Wifi tethering is enabled
+     */
+    public boolean isWifiApEnabled() {
+        return mWifiManager.isWifiApEnabled();
+    }
+
+    /**
+     * Gets {@link WifiConfiguration} for tethering
+     */
+    public WifiConfiguration getWifiApConfig() {
+        return mWifiManager.getWifiApConfiguration();
+    }
+
+    /**
+     * Sets {@link WifiConfiguration} for tethering
+     */
+    public void setWifiApConfig(WifiConfiguration config) {
+        mWifiManager.setWifiApConfiguration(config);
+    }
+
+    /**
+     * Gets the country code in ISO 3166 format.
+     */
+    public String getCountryCode() {
+        return mWifiManager.getCountryCode();
+    }
+
+    /**
+     * Checks if the chipset supports dual frequency band (2.4 GHz and 5 GHz).
+     */
+    public boolean isDualBandSupported() {
+        return mWifiManager.isDualBandSupported();
+    }
+
+    /**
+     * Check if the chipset requires conversion of 5GHz Only apBand to ANY.
+     * @return {@code true} if required, {@code false} otherwise.
+     */
+    public boolean isDualModeSupported() {
+        return mWifiManager.isDualModeSupported();
+    }
+
+    /** Gets the wifi state from {@link WifiManager}. */
     public int getWifiState() {
         return mWifiManager.getWifiState();
     }
 
+    /** Sets whether wifi is enabled. */
     public boolean setWifiEnabled(boolean enabled) {
         return mWifiManager.setWifiEnabled(enabled);
     }
 
+    /** Connects to an public wifi access point. */
     public void connectToPublicWifi(AccessPoint accessPoint, WifiManager.ActionListener listener) {
         accessPoint.generateOpenNetworkConfig();
         mWifiManager.connect(accessPoint.getConfig(), listener);
     }
 
+    /** Connects to a saved access point. */
+    public void connectToSavedWifi(AccessPoint accessPoint, WifiManager.ActionListener listener) {
+        if (accessPoint.isSaved()) {
+            mWifiManager.connect(accessPoint.getConfig(), listener);
+        }
+    }
+
     @Override
     public void onWifiStateChanged(int state) {
-        mListener.onWifiStateChanged(state);
+        for (Listener listener : mListeners) {
+            listener.onWifiStateChanged(state);
+        }
     }
 
     @Override
@@ -169,6 +243,8 @@
 
     @Override
     public void onAccessPointsChanged() {
-        mListener.onAccessPointsChanged();
+        for (Listener listener : mListeners) {
+            listener.onAccessPointsChanged();
+        }
     }
 }
diff --git a/src/com/android/car/settings/wifi/NetworkNamePreferenceController.java b/src/com/android/car/settings/wifi/NetworkNamePreferenceController.java
new file mode 100644
index 0000000..b58f5c7
--- /dev/null
+++ b/src/com/android/car/settings/wifi/NetworkNamePreferenceController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.EditTextPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Business logic for adding/displaying the network name. */
+public class NetworkNamePreferenceController extends PreferenceController<EditTextPreference> {
+
+    /** Action used in the {@link Intent} sent by the {@link LocalBroadcastManager}. */
+    public static final String ACTION_NAME_CHANGE =
+            "com.android.car.settings.wifi.NameChangeAction";
+    /** Key used to store the name of the network. */
+    public static final String KEY_NETWORK_NAME = "network_name";
+
+    public NetworkNamePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<EditTextPreference> getPreferenceType() {
+        return EditTextPreference.class;
+    }
+
+    @Override
+    protected void updateState(EditTextPreference preference) {
+        preference.setSummary(TextUtils.isEmpty(preference.getText()) ? getContext().getString(
+                R.string.default_network_name_summary) : preference.getText());
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(EditTextPreference preference, Object newValue) {
+        String name = newValue.toString();
+        preference.setText(name);
+        notifyNameChange(name);
+        refreshUi();
+        return true;
+    }
+
+    private void notifyNameChange(String newName) {
+        Intent intent = new Intent(ACTION_NAME_CHANGE);
+        intent.putExtra(KEY_NETWORK_NAME, newName);
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcastSync(intent);
+    }
+}
diff --git a/src/com/android/car/settings/wifi/NetworkNameRestrictedPasswordEditTextPreference.java b/src/com/android/car/settings/wifi/NetworkNameRestrictedPasswordEditTextPreference.java
new file mode 100644
index 0000000..8f52152
--- /dev/null
+++ b/src/com/android/car/settings/wifi/NetworkNameRestrictedPasswordEditTextPreference.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.Toast;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.PasswordEditTextPreference;
+
+/**
+ * Custom {@link PasswordEditTextPreference} which doesn't open the password dialog unless the
+ * network name is provided.
+ */
+public class NetworkNameRestrictedPasswordEditTextPreference extends PasswordEditTextPreference {
+
+    private String mNetworkName;
+
+    public NetworkNameRestrictedPasswordEditTextPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public NetworkNameRestrictedPasswordEditTextPreference(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public NetworkNameRestrictedPasswordEditTextPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public NetworkNameRestrictedPasswordEditTextPreference(Context context) {
+        super(context);
+    }
+
+    /** Sets the network name. */
+    public void setNetworkName(String name) {
+        mNetworkName = name;
+    }
+
+    @Override
+    protected void onClick() {
+        if (TextUtils.isEmpty(mNetworkName)) {
+            Toast.makeText(getContext(), R.string.wifi_no_network_name, Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        super.onClick();
+    }
+}
diff --git a/src/com/android/car/settings/wifi/NetworkPasswordPreferenceController.java b/src/com/android/car/settings/wifi/NetworkPasswordPreferenceController.java
new file mode 100644
index 0000000..0616399
--- /dev/null
+++ b/src/com/android/car/settings/wifi/NetworkPasswordPreferenceController.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.text.TextUtils;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.wifi.AccessPoint;
+
+/** Business logic relating to the security type and associated password. */
+public class NetworkPasswordPreferenceController extends
+        PreferenceController<NetworkNameRestrictedPasswordEditTextPreference> {
+
+    private static final Logger LOG = new Logger(NetworkPasswordPreferenceController.class);
+
+    private final BroadcastReceiver mNameChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mNetworkName = intent.getStringExtra(NetworkNamePreferenceController.KEY_NETWORK_NAME);
+            getPreference().setNetworkName(mNetworkName);
+            refreshUi();
+        }
+    };
+
+    private final BroadcastReceiver mSecurityChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mSecurityType = intent.getIntExtra(
+                    NetworkSecurityPreferenceController.KEY_SECURITY_TYPE,
+                    AccessPoint.SECURITY_NONE);
+            refreshUi();
+        }
+    };
+
+    private String mNetworkName;
+    private int mSecurityType = AccessPoint.SECURITY_NONE;
+
+    public NetworkPasswordPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<NetworkNameRestrictedPasswordEditTextPreference> getPreferenceType() {
+        return NetworkNameRestrictedPasswordEditTextPreference.class;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        LocalBroadcastManager.getInstance(getContext()).registerReceiver(mNameChangeReceiver,
+                new IntentFilter(NetworkNamePreferenceController.ACTION_NAME_CHANGE));
+        LocalBroadcastManager.getInstance(getContext()).registerReceiver(mSecurityChangeReceiver,
+                new IntentFilter(NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE));
+    }
+
+    @Override
+    protected void onStopInternal() {
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mNameChangeReceiver);
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mSecurityChangeReceiver);
+    }
+
+    @Override
+    protected void updateState(NetworkNameRestrictedPasswordEditTextPreference preference) {
+        if (TextUtils.isEmpty(mNetworkName)) {
+            getPreference().setDialogTitle(R.string.wifi_password);
+        } else {
+            getPreference().setDialogTitle(mNetworkName);
+        }
+        preference.setVisible(mSecurityType != AccessPoint.SECURITY_NONE);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(
+            NetworkNameRestrictedPasswordEditTextPreference preference, Object newValue) {
+        String password = newValue.toString();
+        int netId = WifiUtil.connectToAccessPoint(getContext(), mNetworkName, mSecurityType,
+                password, /* hidden= */ true);
+
+        LOG.d("connected to netId: " + netId);
+        if (netId != WifiUtil.INVALID_NET_ID) {
+            getFragmentController().goBack();
+        }
+
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/NetworkSecurityPreferenceController.java b/src/com/android/car/settings/wifi/NetworkSecurityPreferenceController.java
new file mode 100644
index 0000000..0d180b2
--- /dev/null
+++ b/src/com/android/car/settings/wifi/NetworkSecurityPreferenceController.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Business logic to select the security type when adding a hidden network. */
+public class NetworkSecurityPreferenceController extends PreferenceController<ListPreference> {
+
+    /** Action used in the {@link Intent} sent by the {@link LocalBroadcastManager}. */
+    public static final String ACTION_SECURITY_CHANGE =
+            "com.android.car.settings.wifi.SecurityChangeAction";
+    /** Key used to store the selected security type. */
+    public static final String KEY_SECURITY_TYPE = "security_type";
+
+    private static final Map<Integer, Integer> SECURITY_TYPE_TO_DESC_RES =
+            createSecurityTypeDescMap();
+
+    private static final List<Integer> SECURITY_TYPES = Arrays.asList(
+            AccessPoint.SECURITY_NONE,
+            AccessPoint.SECURITY_WEP,
+            AccessPoint.SECURITY_PSK,
+            AccessPoint.SECURITY_EAP);
+
+    private CharSequence[] mSecurityTypeNames;
+    private CharSequence[] mSecurityTypeIds;
+    private int mSelectedSecurityType;
+
+    public NetworkSecurityPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ListPreference> getPreferenceType() {
+        return ListPreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        // Security type setup.
+        mSecurityTypeNames = new CharSequence[SECURITY_TYPES.size()];
+        mSecurityTypeIds = new CharSequence[SECURITY_TYPES.size()];
+        mSelectedSecurityType = AccessPoint.SECURITY_NONE;
+
+        for (int i = 0; i < SECURITY_TYPES.size(); i++) {
+            int type = SECURITY_TYPES.get(i);
+            mSecurityTypeNames[i] = getContext().getString(SECURITY_TYPE_TO_DESC_RES.get(type));
+            mSecurityTypeIds[i] = Integer.toString(type);
+        }
+
+        getPreference().setEntries(mSecurityTypeNames);
+        getPreference().setEntryValues(mSecurityTypeIds);
+        getPreference().setDefaultValue(Integer.toString(AccessPoint.SECURITY_NONE));
+    }
+
+    @Override
+    protected void updateState(ListPreference preference) {
+        preference.setSummary(SECURITY_TYPE_TO_DESC_RES.get(mSelectedSecurityType));
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(ListPreference preference, Object newValue) {
+        mSelectedSecurityType = Integer.parseInt(newValue.toString());
+        notifySecurityChange(mSelectedSecurityType);
+        refreshUi();
+        return true;
+    }
+
+    private void notifySecurityChange(int securityType) {
+        Intent intent = new Intent(ACTION_SECURITY_CHANGE);
+        intent.putExtra(KEY_SECURITY_TYPE, securityType);
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcastSync(intent);
+    }
+
+    private static Map<Integer, Integer> createSecurityTypeDescMap() {
+        Map<Integer, Integer> map = new HashMap<>();
+        map.put(AccessPoint.SECURITY_NONE, R.string.wifi_security_none);
+        map.put(AccessPoint.SECURITY_WEP, R.string.wifi_security_wep);
+        map.put(AccessPoint.SECURITY_PSK, R.string.wifi_security_psk_generic);
+        map.put(AccessPoint.SECURITY_EAP, R.string.wifi_security_eap);
+        return map;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiBasePreferenceController.java b/src/com/android/car/settings/wifi/WifiBasePreferenceController.java
new file mode 100644
index 0000000..b53cf39
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiBasePreferenceController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Abstract controls preference for Wifi.
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller
+ *            expects to operate.
+ */
+public abstract class WifiBasePreferenceController<V extends Preference> extends
+        PreferenceController<V> implements CarWifiManager.Listener {
+
+    private CarWifiManager mCarWifiManager;
+
+    public WifiBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mCarWifiManager = new CarWifiManager(getContext());
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mCarWifiManager.addListener(this);
+        mCarWifiManager.start();
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mCarWifiManager.removeListener(this);
+        mCarWifiManager.stop();
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mCarWifiManager.destroy();
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return WifiUtil.isWifiAvailable(getContext()) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    public void onAccessPointsChanged() {
+        // don't care
+    }
+
+    protected CarWifiManager getCarWifiManager() {
+        return mCarWifiManager;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiDetailFragment.java b/src/com/android/car/settings/wifi/WifiDetailFragment.java
deleted file mode 100644
index 07fb50d..0000000
--- a/src/com/android/car/settings/wifi/WifiDetailFragment.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.settings.wifi;
-
-import android.net.NetworkInfo.State;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.support.annotation.StringRes;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.car.list.SimpleTextLineItem;
-import com.android.car.list.TypedPagedListAdapter;
-import com.android.car.settings.R;
-import com.android.car.settings.common.ListSettingsFragment;
-import com.android.car.settings.common.Logger;
-import com.android.settingslib.wifi.AccessPoint;
-
-import java.util.ArrayList;
-
-/**
- * Shows details about a wifi network, including actions related to the network,
- * e.g. ignore, disconnect, etc. The intent should include information about
- * access point, use that to render UI, e.g. show SSID etc.
- */
-public class WifiDetailFragment extends ListSettingsFragment {
-    public static final String EXTRA_AP_STATE = "extra_ap_state";
-    private static final Logger LOG = new Logger(WifiDetailFragment.class);
-
-    private AccessPoint mAccessPoint;
-    private WifiManager mWifiManager;
-
-    private class ActionFailListener implements WifiManager.ActionListener {
-        @StringRes
-        private final int mMessageResId;
-
-        public ActionFailListener(@StringRes int messageResId) {
-            mMessageResId = messageResId;
-        }
-
-        @Override
-        public void onSuccess() {
-        }
-
-        @Override
-        public void onFailure(int reason) {
-            Toast.makeText(getContext(),
-                    R.string.wifi_failed_connect_message,
-                    Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    public static WifiDetailFragment getInstance(AccessPoint accessPoint) {
-        WifiDetailFragment wifiDetailFragment = new WifiDetailFragment();
-        Bundle bundle = ListSettingsFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.wifi_settings);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
-        Bundle accessPointState = new Bundle();
-        accessPoint.saveWifiState(accessPointState);
-        bundle.putBundle(EXTRA_AP_STATE, accessPointState);
-        wifiDetailFragment.setArguments(bundle);
-        return wifiDetailFragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mAccessPoint = new AccessPoint(getContext(), getArguments().getBundle(EXTRA_AP_STATE));
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        mWifiManager = getContext().getSystemService(WifiManager.class);
-
-        super.onActivityCreated(savedInstanceState);
-        ((TextView) getActivity().findViewById(R.id.title)).setText(mAccessPoint.getSsid());
-        Button forgetButton = (Button) getActivity().findViewById(R.id.action_button1);
-        forgetButton.setText(R.string.forget);
-        forgetButton.setOnClickListener(v -> {
-            forget();
-            getFragmentController().goBack();
-        });
-
-        if (mAccessPoint.isSaved() && !mAccessPoint.isActive()) {
-            Button connectButton = (Button) getActivity().findViewById(R.id.action_button2);
-            connectButton.setVisibility(View.VISIBLE);
-            connectButton.setText(R.string.wifi_setup_connect);
-            connectButton.setOnClickListener(v -> {
-                mWifiManager.connect(mAccessPoint.getConfig(),
-                        new ActionFailListener(R.string.wifi_failed_connect_message));
-                getFragmentController().goBack();
-            });
-        }
-    }
-
-    @Override
-    public ArrayList<TypedPagedListAdapter.LineItem> getLineItems() {
-        ArrayList<TypedPagedListAdapter.LineItem> lineItems = new ArrayList<>();
-        lineItems.add(
-                new SimpleTextLineItem(getText(R.string.wifi_status), mAccessPoint.getSummary()));
-        lineItems.add(
-                new SimpleTextLineItem(getText(R.string.wifi_signal), getSignalString()));
-        lineItems.add(new SimpleTextLineItem(getText(R.string.wifi_security),
-                mAccessPoint.getSecurityString(/* concise= */ true)));
-        return lineItems;
-    }
-
-    private String getSignalString() {
-        String[] signalStrings = getResources().getStringArray(R.array.wifi_signals);
-
-        int level = WifiManager.calculateSignalLevel(
-                mAccessPoint.getRssi(), signalStrings.length);
-        return signalStrings[level];
-    }
-
-    private void forget() {
-        if (!mAccessPoint.isSaved()) {
-            if (mAccessPoint.getNetworkInfo() != null &&
-                    mAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
-                // Network is active but has no network ID - must be ephemeral.
-                mWifiManager.disableEphemeralNetwork(
-                        AccessPoint.convertToQuotedString(mAccessPoint.getSsidStr()));
-            } else {
-                // Should not happen, but a monkey seems to trigger it
-                LOG.e("Failed to forget invalid network " + mAccessPoint.getConfig());
-                return;
-            }
-        } else {
-            mWifiManager.forget(mAccessPoint.getConfig().networkId,
-                    new ActionFailListener(R.string.wifi_failed_forget_message));
-        }
-    }
-}
diff --git a/src/com/android/car/settings/wifi/WifiEntryPreferenceController.java b/src/com/android/car/settings/wifi/WifiEntryPreferenceController.java
new file mode 100644
index 0000000..a564713
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiEntryPreferenceController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.MasterSwitchPreference;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller which determines if the top level entry into Wi-Fi settings should be displayed
+ * based on device capabilities.
+ */
+public class WifiEntryPreferenceController extends PreferenceController<MasterSwitchPreference> {
+
+    private CarWifiManager mCarWifiManager;
+
+    public WifiEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCarWifiManager = new CarWifiManager(context);
+    }
+
+    @Override
+    protected Class<MasterSwitchPreference> getPreferenceType() {
+        return MasterSwitchPreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        getPreference().setSwitchToggleListener((preference, isChecked) -> {
+            if (isChecked != mCarWifiManager.isWifiEnabled()) {
+                mCarWifiManager.setWifiEnabled(isChecked);
+            }
+        });
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return WifiUtil.isWifiAvailable(getContext()) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    protected void updateState(MasterSwitchPreference preference) {
+        preference.setSwitchChecked(mCarWifiManager.isWifiEnabled());
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiSettingsFragment.java b/src/com/android/car/settings/wifi/WifiSettingsFragment.java
index 7bb7ee3..52706dd 100644
--- a/src/com/android/car/settings/wifi/WifiSettingsFragment.java
+++ b/src/com/android/car/settings/wifi/WifiSettingsFragment.java
@@ -15,94 +15,65 @@
  */
 package com.android.car.settings.wifi;
 
-import android.annotation.NonNull;
-import android.annotation.StringRes;
-import android.car.drivingstate.CarUxRestrictions;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.ProgressBar;
 import android.widget.Switch;
-import android.widget.TextView;
-import android.widget.ViewSwitcher;
 
-import androidx.car.widget.PagedListView;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
-import com.android.car.settings.common.CarUxRestrictionsHelper;
+import com.android.car.settings.common.SettingsFragment;
 
 /**
  * Main page to host Wifi related preferences.
  */
-public class WifiSettingsFragment extends BaseFragment implements CarWifiManager.Listener {
-    private CarWifiManager mCarWifiManager;
-    private AccessPointListAdapter mAdapter;
-    private Switch mWifiSwitch;
-    private ProgressBar mProgressBar;
-    private PagedListView mListView;
-    private TextView mMessageView;
-    private ViewSwitcher mViewSwitcher;
-    private boolean mShowSavedApOnly;
+public class WifiSettingsFragment extends SettingsFragment
+        implements CarWifiManager.Listener {
 
-    /**
-     * Gets a new instance of this object.
-     */
-    public static WifiSettingsFragment newInstance() {
-        WifiSettingsFragment wifiSettingsFragment = new WifiSettingsFragment();
-        Bundle bundle = BaseFragment.getBundle();
-        bundle.putInt(EXTRA_TITLE_ID, R.string.wifi_settings);
-        bundle.putInt(EXTRA_LAYOUT, R.layout.wifi_list);
-        bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_toggle);
-        wifiSettingsFragment.setArguments(bundle);
-        return wifiSettingsFragment;
+    private static final int SEARCHING_DELAY_MILLIS = 1700;
+
+    private CarWifiManager mCarWifiManager;
+    private ProgressBar mProgressBar;
+    private Switch mWifiSwitch;
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_toggle;
     }
 
-    /**
-     * Shows only saved wifi network.
-     */
-    public WifiSettingsFragment showSavedApOnly(boolean showSavedApOnly) {
-        mShowSavedApOnly = showSavedApOnly;
-        return this;
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_list_fragment;
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
-        mCarWifiManager = new CarWifiManager(getContext(), /* listener= */ this);
+        mCarWifiManager = new CarWifiManager(getContext());
 
-        mProgressBar = (ProgressBar) getView().findViewById(R.id.wifi_search_progress);
-        mListView = (PagedListView) getView().findViewById(R.id.list);
-        mMessageView = (TextView) getView().findViewById(R.id.message);
-        mViewSwitcher = (ViewSwitcher) getView().findViewById(R.id.view_switcher);
+        mProgressBar = requireActivity().findViewById(R.id.progress_bar);
         setupWifiSwitch();
-        if (mCarWifiManager.isWifiEnabled()) {
-            showList();
-            setProgressBarVisible(true);
-        } else {
-            showMessage(R.string.wifi_disabled);
-        }
-        mAdapter = new AccessPointListAdapter(
-                getContext(),
-                mCarWifiManager,
-                mShowSavedApOnly
-                        ? mCarWifiManager.getSavedAccessPoints()
-                        : mCarWifiManager.getAllAccessPoints(),
-                getFragmentController());
-        mAdapter.showAddNetworkRow(!mShowSavedApOnly);
-        mListView.setAdapter(mAdapter);
     }
 
     @Override
     public void onStart() {
         super.onStart();
+        mCarWifiManager.addListener(this);
         mCarWifiManager.start();
+        onWifiStateChanged(mCarWifiManager.getWifiState());
     }
 
     @Override
     public void onStop() {
         super.onStop();
+        mCarWifiManager.removeListener(this);
         mCarWifiManager.stop();
+        mProgressBar.setVisibility(View.GONE);
     }
 
     @Override
@@ -113,7 +84,8 @@
 
     @Override
     public void onAccessPointsChanged() {
-        refreshData();
+        mProgressBar.setVisibility(View.VISIBLE);
+        getView().postDelayed(() -> mProgressBar.setVisibility(View.GONE), SEARCHING_DELAY_MILLIS);
     }
 
     @Override
@@ -121,75 +93,19 @@
         mWifiSwitch.setChecked(mCarWifiManager.isWifiEnabled());
         switch (state) {
             case WifiManager.WIFI_STATE_ENABLING:
-                showList();
-                setProgressBarVisible(true);
-                break;
-            case WifiManager.WIFI_STATE_DISABLED:
-                setProgressBarVisible(false);
-                showMessage(R.string.wifi_disabled);
+                mProgressBar.setVisibility(View.VISIBLE);
                 break;
             default:
-                showList();
-        }
-    }
-
-    /**
-     * This fragment will adapt to restriction, so can always be shown.
-     */
-    @Override
-    public boolean canBeShown(CarUxRestrictions carUxRestrictions) {
-        return true;
-    }
-
-    @Override
-    public void onUxRestrictionChanged(@NonNull CarUxRestrictions carUxRestrictions) {
-        mShowSavedApOnly = CarUxRestrictionsHelper.isNoSetup(carUxRestrictions);
-        refreshData();
-    }
-
-    private  void setProgressBarVisible(boolean visible) {
-        if (mProgressBar != null) {
-            mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
-        }
-    }
-
-    private void refreshData() {
-        if (mAdapter != null) {
-            mAdapter.showAddNetworkRow(!mShowSavedApOnly);
-            mAdapter.updateAccessPoints(mShowSavedApOnly
-                    ? mCarWifiManager.getSavedAccessPoints()
-                    : mCarWifiManager.getAllAccessPoints());
-            // if the list is empty, keep showing the progress bar, the list should reset
-            // every couple seconds.
-            // TODO: Consider show a message in the list view place.
-            if (!mAdapter.isEmpty()) {
-                setProgressBarVisible(false);
-            }
-        }
-        if (mCarWifiManager != null) {
-            mWifiSwitch.setChecked(mCarWifiManager.isWifiEnabled());
-        }
-    }
-
-    private void showMessage(@StringRes int resId) {
-        if (mViewSwitcher.getCurrentView() != mMessageView) {
-            mViewSwitcher.showNext();
-        }
-        mMessageView.setText(getResources().getString(resId));
-    }
-
-    private void showList() {
-        if (mViewSwitcher.getCurrentView() != mListView) {
-            mViewSwitcher.showPrevious();
+                mProgressBar.setVisibility(View.GONE);
         }
     }
 
     private void setupWifiSwitch() {
-        mWifiSwitch = (Switch) getActivity().findViewById(R.id.toggle_switch);
+        mWifiSwitch = getActivity().findViewById(R.id.toggle_switch);
         mWifiSwitch.setChecked(mCarWifiManager.isWifiEnabled());
         mWifiSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (mWifiSwitch.isChecked() != mCarWifiManager.isWifiEnabled()) {
-                mCarWifiManager.setWifiEnabled(mWifiSwitch.isChecked());
+            if (isChecked != mCarWifiManager.isWifiEnabled()) {
+                mCarWifiManager.setWifiEnabled(isChecked);
             }
         });
     }
diff --git a/src/com/android/car/settings/wifi/WifiStatusPreferenceController.java b/src/com/android/car/settings/wifi/WifiStatusPreferenceController.java
new file mode 100644
index 0000000..4bc0b56
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiStatusPreferenceController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Controls preference when Wifi is in disabled or enabling state
+ */
+public class WifiStatusPreferenceController extends WifiBasePreferenceController<Preference> {
+
+    public WifiStatusPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public void onWifiStateChanged(int state) {
+        switch (state) {
+            case WifiManager.WIFI_STATE_DISABLED:
+                getPreference().setVisible(true);
+                getPreference().setTitle(R.string.wifi_disabled);
+                break;
+            case WifiManager.WIFI_STATE_ENABLING:
+                getPreference().setVisible(true);
+                getPreference().setTitle(R.string.loading_wifi_list);
+                break;
+            default:
+                getPreference().setVisible(false);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiTetherApBandPreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherApBandPreferenceController.java
new file mode 100644
index 0000000..1410168
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiTetherApBandPreferenceController.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Controls WiFi Hotspot AP Band configuration.
+ */
+public class WifiTetherApBandPreferenceController extends
+        WifiTetherBasePreferenceController<ListPreference> {
+
+    private String[] mBandEntries;
+    private String[] mBandSummaries;
+    private int mBandIndex;
+    private boolean mIsDualMode;
+
+    public WifiTetherApBandPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ListPreference> getPreferenceType() {
+        return ListPreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        super.onCreateInternal();
+        mIsDualMode = getCarWifiManager().isDualModeSupported();
+        updatePreferenceEntries();
+        getPreference().setEntries(mBandSummaries);
+        getPreference().setEntryValues(mBandEntries);
+    }
+
+    @Override
+    public void updateState(ListPreference preference) {
+        super.updateState(preference);
+
+        WifiConfiguration config = getCarWifiApConfig();
+        if (config == null) {
+            mBandIndex = 0;
+        } else if (is5GhzBandSupported()) {
+            mBandIndex = validateSelection(config.apBand);
+        } else {
+            config.apBand = 0;
+            setCarWifiApConfig(config);
+            mBandIndex = config.apBand;
+        }
+
+        if (!is5GhzBandSupported()) {
+            preference.setEnabled(false);
+            preference.setSummary(R.string.wifi_ap_choose_2G);
+        } else {
+            preference.setValue(Integer.toString(config.apBand));
+            preference.setSummary(getSummary());
+        }
+
+    }
+
+    @Override
+    protected String getSummary() {
+        if (is5GhzBandSupported()) {
+            if (mBandIndex != WifiConfiguration.AP_BAND_ANY) {
+                return mBandSummaries[mBandIndex];
+            } else {
+                return getContext().getString(R.string.wifi_ap_prefer_5G);
+            }
+        } else {
+            return getContext().getString(R.string.wifi_ap_choose_2G);
+        }
+    }
+
+    @Override
+    protected String getDefaultSummary() {
+        return null;
+    }
+
+    @Override
+    public boolean handlePreferenceChanged(ListPreference preference, Object newValue) {
+        mBandIndex = validateSelection(Integer.parseInt((String) newValue));
+        updateApBand(); // updating AP band because mBandIndex may have been assigned a new value.
+        refreshUi();
+        return true;
+    }
+
+    private int validateSelection(int band) {
+        // Reset the band to 2.4 GHz if we get a weird config back to avoid a crash.
+        boolean isDualMode = getCarWifiManager().isDualModeSupported();
+
+        // unsupported states:
+        // 1: no dual mode means we can't have AP_BAND_ANY - default to 5GHZ
+        // 2: no 5 GHZ support means we can't have AP_BAND_5GHZ - default to 2GHZ
+        // 3: With Dual mode support we can't have AP_BAND_5GHZ - default to ANY
+        if (!isDualMode && WifiConfiguration.AP_BAND_ANY == band) {
+            return WifiConfiguration.AP_BAND_5GHZ;
+        } else if (!is5GhzBandSupported() && WifiConfiguration.AP_BAND_5GHZ == band) {
+            return WifiConfiguration.AP_BAND_2GHZ;
+        } else if (isDualMode && WifiConfiguration.AP_BAND_5GHZ == band) {
+            return WifiConfiguration.AP_BAND_ANY;
+        }
+
+        return band;
+    }
+
+    private void updatePreferenceEntries() {
+        Resources res = getContext().getResources();
+        int entriesRes = R.array.wifi_ap_band_config_full;
+        int summariesRes = R.array.wifi_ap_band_summary_full;
+        // change the list options if this is a dual mode device
+        if (mIsDualMode) {
+            entriesRes = R.array.wifi_ap_band_dual_mode;
+            summariesRes = R.array.wifi_ap_band_dual_mode_summary;
+        }
+        mBandEntries = res.getStringArray(entriesRes);
+        mBandSummaries = res.getStringArray(summariesRes);
+    }
+
+    private void updateApBand() {
+        WifiConfiguration config = getCarWifiApConfig();
+        config.apBand = mBandIndex;
+        setCarWifiApConfig(config);
+        if (mBandIndex == WifiConfiguration.AP_BAND_ANY) {
+            getPreference().setValue(mBandEntries[WifiConfiguration.AP_BAND_2GHZ]);
+        } else {
+            getPreference().setValue(mBandEntries[mBandIndex]);
+        }
+    }
+
+    private boolean is5GhzBandSupported() {
+        String countryCode = getCarWifiManager().getCountryCode();
+        return getCarWifiManager().isDualBandSupported() && countryCode != null;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiTetherAutoOffPreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherAutoOffPreferenceController.java
new file mode 100644
index 0000000..ef19a3f
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiTetherAutoOffPreferenceController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controls wifi tethering auto off configuration
+ */
+public class WifiTetherAutoOffPreferenceController extends
+        PreferenceController<TwoStatePreference> {
+
+    public WifiTetherAutoOffPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        boolean settingsOn = Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) != 0;
+        preference.setChecked(settingsOn);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean settingsOn = (Boolean) newValue;
+        Settings.Global.putInt(getContext().getContentResolver(),
+                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, settingsOn ? 1 : 0);
+        return true;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiTetherBasePreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherBasePreferenceController.java
new file mode 100644
index 0000000..dd74f4b
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiTetherBasePreferenceController.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.text.TextUtils;
+
+import androidx.annotation.CallSuper;
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Shared business logic for preference controllers related to Wifi Tethering
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller
+ *            expects to operate.
+ */
+public abstract class WifiTetherBasePreferenceController<V extends Preference> extends
+        PreferenceController<V> {
+
+    private CarWifiManager mCarWifiManager;
+
+    public WifiTetherBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    @CallSuper
+    protected void onCreateInternal() {
+        mCarWifiManager = new CarWifiManager(getContext());
+    }
+
+    @Override
+    @CallSuper
+    protected void onStartInternal() {
+        mCarWifiManager.start();
+        getPreference().setPersistent(true);
+    }
+
+    @Override
+    @CallSuper
+    protected void onStopInternal() {
+        mCarWifiManager.stop();
+    }
+
+    @Override
+    @CallSuper
+    protected void onDestroyInternal() {
+        mCarWifiManager.destroy();
+    }
+
+    @Override
+    @CallSuper
+    protected void updateState(V preference) {
+        String summary = getSummary();
+        String defaultSummary = getDefaultSummary();
+
+        if (TextUtils.isEmpty(summary)) {
+            preference.setSummary(defaultSummary);
+        } else {
+            preference.setSummary(summary);
+        }
+    }
+
+    protected WifiConfiguration getCarWifiApConfig() {
+        return mCarWifiManager.getWifiApConfig();
+    }
+
+    protected void setCarWifiApConfig(WifiConfiguration configuration) {
+        mCarWifiManager.setWifiApConfig(configuration);
+    }
+
+    protected CarWifiManager getCarWifiManager() {
+        return mCarWifiManager;
+    }
+
+    protected abstract String getSummary();
+
+    protected abstract String getDefaultSummary();
+}
diff --git a/src/com/android/car/settings/wifi/WifiTetherFragment.java b/src/com/android/car/settings/wifi/WifiTetherFragment.java
new file mode 100644
index 0000000..f8979e1
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiTetherFragment.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.ProgressBar;
+import android.widget.Switch;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Fragment to host tethering-related preferences.
+ */
+public class WifiTetherFragment extends SettingsFragment implements Switch.OnCheckedChangeListener {
+
+    private CarWifiManager mCarWifiManager;
+    private ConnectivityManager mConnectivityManager;
+    private ProgressBar mProgressBar;
+    private Switch mTetherSwitch;
+
+    private final ConnectivityManager.OnStartTetheringCallback mOnStartTetheringCallback =
+            new ConnectivityManager.OnStartTetheringCallback() {
+                @Override
+                public void onTetheringFailed() {
+                    super.onTetheringFailed();
+                    mTetherSwitch.setChecked(false);
+                    mTetherSwitch.setEnabled(true);
+                }
+            };
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_toggle;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_tether_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        mCarWifiManager = new CarWifiManager(context);
+        mConnectivityManager = (ConnectivityManager) getContext().getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        mProgressBar = getActivity().findViewById(R.id.progress_bar);
+        mTetherSwitch = getActivity().findViewById(R.id.toggle_switch);
+        setupTetherSwitch();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        mCarWifiManager.start();
+        getContext().registerReceiver(mReceiver,
+                new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION));
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mCarWifiManager.stop();
+        getContext().unregisterReceiver(mReceiver);
+        mProgressBar.setVisibility(View.GONE);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mCarWifiManager.destroy();
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
+        if (!isChecked) {
+            mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+        } else {
+            mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI,
+                    /* showProvisioningUi= */ true,
+                    mOnStartTetheringCallback, new Handler(Looper.getMainLooper()));
+        }
+    }
+
+    protected void setupTetherSwitch() {
+        mTetherSwitch.setChecked(mCarWifiManager.isWifiApEnabled());
+        mTetherSwitch.setOnCheckedChangeListener(this);
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int state = intent.getIntExtra(
+                    WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED);
+            handleWifiApStateChanged(state);
+        }
+    };
+
+    private void handleWifiApStateChanged(int state) {
+        switch (state) {
+            case WifiManager.WIFI_AP_STATE_ENABLING:
+                mTetherSwitch.setEnabled(false);
+                break;
+            case WifiManager.WIFI_AP_STATE_ENABLED:
+                mTetherSwitch.setEnabled(true);
+                if (!mTetherSwitch.isChecked()) {
+                    mTetherSwitch.setChecked(true);
+                }
+                break;
+            case WifiManager.WIFI_AP_STATE_DISABLING:
+                mTetherSwitch.setEnabled(false);
+                if (mTetherSwitch.isChecked()) {
+                    mTetherSwitch.setChecked(false);
+                }
+                break;
+            case WifiManager.WIFI_AP_STATE_DISABLED:
+                mTetherSwitch.setChecked(false);
+                mTetherSwitch.setEnabled(true);
+                break;
+            default:
+                mTetherSwitch.setChecked(false);
+                mTetherSwitch.setEnabled(true);
+                break;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiTetherNamePreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherNamePreferenceController.java
new file mode 100644
index 0000000..6bc2f58
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiTetherNamePreferenceController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.ValidatedEditTextPreference;
+
+/**
+ * Controls WiFi Hotspot name configuration. When Hotspot is enabled, this name that will be
+ * displayed as the Access Point to the Hotspot.
+ */
+public class WifiTetherNamePreferenceController extends
+        WifiTetherBasePreferenceController<ValidatedEditTextPreference> {
+
+    private static final int HOTSPOT_NAME_MIN_LENGTH = 1;
+    private static final int HOTSPOT_NAME_MAX_LENGTH = 32;
+    private static final ValidatedEditTextPreference.Validator NAME_VALIDATOR =
+            value -> value.length() >= HOTSPOT_NAME_MIN_LENGTH
+                    && value.length() <= HOTSPOT_NAME_MAX_LENGTH;
+
+    private String mName;
+
+    public WifiTetherNamePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ValidatedEditTextPreference> getPreferenceType() {
+        return ValidatedEditTextPreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        super.onCreateInternal();
+        getPreference().setValidator(NAME_VALIDATOR);
+        mName = getCarWifiApConfig().SSID;
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(ValidatedEditTextPreference preference,
+            Object newValue) {
+        mName = newValue.toString();
+        updateSSID(mName);
+        refreshUi();
+        return true;
+    }
+
+    @Override
+    protected void updateState(ValidatedEditTextPreference preference) {
+        super.updateState(preference);
+        preference.setText(mName);
+    }
+
+    private void updateSSID(String ssid) {
+        WifiConfiguration config = getCarWifiApConfig();
+        config.SSID = ssid;
+        setCarWifiApConfig(config);
+    }
+
+    @Override
+    protected String getSummary() {
+        return mName;
+    }
+
+    @Override
+    protected String getDefaultSummary() {
+        return null;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceController.java
new file mode 100644
index 0000000..14f83e0
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceController.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.net.wifi.WifiConfiguration;
+import android.text.InputType;
+import android.text.TextUtils;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.ValidatedEditTextPreference;
+
+import java.util.UUID;
+
+/**
+ * Controls Wifi Hotspot password configuration.
+ *
+ * <p>Note: This controller uses {@link ValidatedEditTextPreference} as opposed to
+ * PasswordEditTextPreference because the input is not obscured by default, and the user is setting
+ * their own password, as opposed to entering password for authentication.
+ */
+public class WifiTetherPasswordPreferenceController extends
+        WifiTetherBasePreferenceController<ValidatedEditTextPreference> {
+
+    protected static final String SHARED_PREFERENCE_PATH =
+            "com.android.car.settings.wifi.WifiTetherPreferenceController";
+    protected static final String KEY_SAVED_PASSWORD =
+            "com.android.car.settings.wifi.SAVED_PASSWORD";
+
+    private static final int HOTSPOT_PASSWORD_MIN_LENGTH = 8;
+    private static final int HOTSPOT_PASSWORD_MAX_LENGTH = 63;
+    private static final ValidatedEditTextPreference.Validator PASSWORD_VALIDATOR =
+            value -> value.length() >= HOTSPOT_PASSWORD_MIN_LENGTH
+                    && value.length() <= HOTSPOT_PASSWORD_MAX_LENGTH;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mSecurityType = intent.getIntExtra(
+                    WifiTetherSecurityPreferenceController.KEY_SECURITY_TYPE,
+                    /* defaultValue= */ WifiConfiguration.KeyMgmt.NONE);
+            syncPassword();
+        }
+    };
+    private final SharedPreferences mSharedPreferences =
+            getContext().getSharedPreferences(SHARED_PREFERENCE_PATH, Context.MODE_PRIVATE);
+
+    private String mPassword;
+    private int mSecurityType;
+
+    public WifiTetherPasswordPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ValidatedEditTextPreference> getPreferenceType() {
+        return ValidatedEditTextPreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        super.onCreateInternal();
+
+        getPreference().setValidator(PASSWORD_VALIDATOR);
+        mSecurityType = getCarWifiApConfig().getAuthType();
+        syncPassword();
+    }
+
+    @Override
+    protected void onStartInternal() {
+        LocalBroadcastManager.getInstance(getContext()).registerReceiver(mReceiver,
+                new IntentFilter(
+                        WifiTetherSecurityPreferenceController.ACTION_SECURITY_TYPE_CHANGED));
+    }
+
+    @Override
+    protected void onStopInternal() {
+        super.onStopInternal();
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(ValidatedEditTextPreference preference,
+            Object newValue) {
+        mPassword = newValue.toString();
+        updatePassword(mPassword);
+        refreshUi();
+        return true;
+    }
+
+    @Override
+    protected void updateState(ValidatedEditTextPreference preference) {
+        super.updateState(preference);
+        updatePasswordDisplay();
+        if (TextUtils.isEmpty(mPassword)) {
+            preference.setSummaryInputType(InputType.TYPE_CLASS_TEXT);
+        } else {
+            preference.setSummaryInputType(
+                    InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+        }
+    }
+
+    @Override
+    protected String getSummary() {
+        return mPassword;
+    }
+
+    @Override
+    protected String getDefaultSummary() {
+        return getContext().getString(R.string.default_password_summary);
+    }
+
+    private void syncPassword() {
+        mPassword = getSyncedPassword();
+        updatePassword(mPassword);
+        refreshUi();
+    }
+
+    private String getSyncedPassword() {
+        if (getCarWifiApConfig().getAuthType() == WifiConfiguration.KeyMgmt.NONE) {
+            return null;
+        }
+
+        if (!TextUtils.isEmpty(getCarWifiApConfig().preSharedKey)) {
+            return getCarWifiApConfig().preSharedKey;
+        }
+
+        if (!TextUtils.isEmpty(
+                mSharedPreferences.getString(KEY_SAVED_PASSWORD, /* defaultValue= */ null))) {
+            return mSharedPreferences.getString(KEY_SAVED_PASSWORD, /* defaultValue= */ null);
+        }
+
+        return generateRandomPassword();
+    }
+
+    private static String generateRandomPassword() {
+        String randomUUID = UUID.randomUUID().toString();
+        // First 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+        return randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
+    }
+
+    private void updatePassword(String password) {
+        WifiConfiguration config = getCarWifiApConfig();
+        config.preSharedKey = password;
+        setCarWifiApConfig(config);
+
+        if (!TextUtils.isEmpty(password)) {
+            mSharedPreferences.edit().putString(KEY_SAVED_PASSWORD, password).commit();
+        }
+    }
+
+    private void updatePasswordDisplay() {
+        getPreference().setText(mPassword);
+        getPreference().setVisible(mSecurityType != WifiConfiguration.KeyMgmt.NONE);
+        getPreference().setSummary(getSummary());
+    }
+
+}
diff --git a/src/com/android/car/settings/wifi/WifiTetherPreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherPreferenceController.java
new file mode 100644
index 0000000..c4f4a8b
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiTetherPreferenceController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.ConnectivityManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controls the availability of wifi tethering preference based on whether tethering is supported
+ */
+public class WifiTetherPreferenceController extends PreferenceController<Preference> {
+
+    private final ConnectivityManager mConnectivityManager =
+            (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+
+    public WifiTetherPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return  mConnectivityManager.isTetheringSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiTetherSecurityPreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherSecurityPreferenceController.java
new file mode 100644
index 0000000..618bc25
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiTetherSecurityPreferenceController.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Controls WiFi Hotspot Security Type configuration.
+ */
+public class WifiTetherSecurityPreferenceController extends
+        WifiTetherBasePreferenceController<ListPreference> {
+
+    public static final String KEY_SECURITY_TYPE = "KEY_SECURITY_TYPE";
+    public static final String ACTION_SECURITY_TYPE_CHANGED =
+            "com.android.car.settings.wifi.ACTION_WIFI_TETHER_SECURITY_TYPE_CHANGED";
+
+    private int mSecurityType;
+
+    public WifiTetherSecurityPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ListPreference> getPreferenceType() {
+        return ListPreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        super.onCreateInternal();
+        mSecurityType = getCarWifiApConfig().getAuthType();
+        getPreference().setEntries(
+                getContext().getResources().getStringArray(R.array.wifi_tether_security));
+        String[] entryValues = {Integer.toString(WifiConfiguration.KeyMgmt.WPA2_PSK),
+                Integer.toString(WifiConfiguration.KeyMgmt.NONE)};
+        getPreference().setEntryValues(entryValues);
+        getPreference().setValue(String.valueOf(mSecurityType));
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(ListPreference preference,
+            Object newValue) {
+        mSecurityType = Integer.parseInt(newValue.toString());
+        updateSecurityType();
+        refreshUi();
+        return true;
+    }
+
+    @Override
+    protected void updateState(ListPreference preference) {
+        super.updateState(preference);
+        preference.setValue(Integer.toString(mSecurityType));
+    }
+
+    @Override
+    protected String getSummary() {
+        int stringResId = mSecurityType == WifiConfiguration.KeyMgmt.WPA2_PSK
+                ? R.string.wifi_hotspot_wpa2_personal : R.string.wifi_hotspot_security_none;
+        return getContext().getString(stringResId);
+    }
+
+    @Override
+    protected String getDefaultSummary() {
+        return null;
+    }
+
+    private void updateSecurityType() {
+        WifiConfiguration config = getCarWifiApConfig();
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(mSecurityType);
+
+        if (mSecurityType == WifiConfiguration.KeyMgmt.NONE) {
+            config.preSharedKey = "";
+        } else {
+            config.preSharedKey = getSavedPassword();
+        }
+
+        setCarWifiApConfig(config);
+        broadcastSecurityTypeChanged();
+    }
+
+    private void broadcastSecurityTypeChanged() {
+        Intent intent = new Intent(ACTION_SECURITY_TYPE_CHANGED);
+        intent.putExtra(KEY_SECURITY_TYPE, mSecurityType);
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+    }
+
+    private String getSavedPassword() {
+        SharedPreferences sp = getContext().getSharedPreferences(
+                WifiTetherPasswordPreferenceController.SHARED_PREFERENCE_PATH,
+                Context.MODE_PRIVATE);
+        String savedPassword =
+                sp.getString(WifiTetherPasswordPreferenceController.KEY_SAVED_PASSWORD,
+                        /* defaultValue= */ null);
+        return savedPassword;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/WifiUtil.java b/src/com/android/car/settings/wifi/WifiUtil.java
index 46121fb..15f0134 100644
--- a/src/com/android/car/settings/wifi/WifiUtil.java
+++ b/src/com/android/car/settings/wifi/WifiUtil.java
@@ -15,18 +15,34 @@
  */
 package com.android.car.settings.wifi;
 
-import android.annotation.DrawableRes;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
-import android.support.annotation.StringRes;
+import android.provider.Settings;
+import android.widget.Toast;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.StringRes;
 
 import com.android.car.settings.R;
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.regex.Pattern;
 
 /**
  * A collections of util functions for WIFI.
  */
 public class WifiUtil {
+
+    /** Value that is returned when we fail to connect wifi. */
+    public static final int INVALID_NET_ID = -1;
+    private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9A-F]+$");
+
     @DrawableRes
     public static int getIconRes(int state) {
         switch (state) {
@@ -71,4 +87,121 @@
     public static boolean isWifiAvailable(Context context) {
         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
     }
+
+    /**
+     * Gets a unique key for a {@link AccessPoint}.
+     */
+    public static String getKey(AccessPoint accessPoint) {
+        return String.valueOf(accessPoint.hashCode());
+    }
+
+    /**
+     * This method is a stripped and negated version of WifiConfigStore.canModifyNetwork.
+     *
+     * @param context Context of caller
+     * @param config  The WiFi config.
+     * @return {@code true} if Settings cannot modify the config due to lockDown.
+     */
+    public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) {
+        if (config == null) {
+            return false;
+        }
+
+        final DevicePolicyManager dpm =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        final PackageManager pm = context.getPackageManager();
+
+        // Check if device has DPM capability. If it has and dpm is still null, then we
+        // treat this case with suspicion and bail out.
+        if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
+            return true;
+        }
+
+        boolean isConfigEligibleForLockdown = false;
+        if (dpm != null) {
+            final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
+            if (deviceOwner != null) {
+                final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
+                try {
+                    final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
+                            deviceOwnerUserId);
+                    isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
+                } catch (PackageManager.NameNotFoundException e) {
+                    // don't care
+                }
+            }
+        }
+        if (!isConfigEligibleForLockdown) {
+            return false;
+        }
+
+        final ContentResolver resolver = context.getContentResolver();
+        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
+        return isLockdownFeatureEnabled;
+    }
+
+    /**
+     * Returns {@code true} if the provided NetworkCapabilities indicate a captive portal network.
+     */
+    public static boolean canSignIntoNetwork(NetworkCapabilities capabilities) {
+        return (capabilities != null
+                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL));
+    }
+
+    /**
+     * Returns netId. -1 if connection fails.
+     */
+    public static int connectToAccessPoint(Context context, String ssid, int security,
+            String password, boolean hidden) {
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.SSID = String.format("\"%s\"", ssid);
+        wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+        wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
+        wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+        wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
+        wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
+        wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
+        wifiConfig.hiddenSSID = hidden;
+        switch (security) {
+            case AccessPoint.SECURITY_NONE:
+                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+                wifiConfig.allowedAuthAlgorithms.clear();
+                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
+                break;
+            case AccessPoint.SECURITY_WEP:
+                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+                wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+                wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
+                wifiConfig.wepKeys[0] = isHexString(password) ? password
+                        : "\"" + password + "\"";
+                wifiConfig.wepTxKeyIndex = 0;
+                break;
+            case AccessPoint.SECURITY_PSK:
+            case AccessPoint.SECURITY_EAP:
+                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
+                wifiConfig.preSharedKey = String.format("\"%s\"", password);
+                break;
+            default:
+                throw new IllegalArgumentException("invalid security type");
+        }
+        int netId = wifiManager.addNetwork(wifiConfig);
+        // This only means wifiManager failed writing the new wifiConfig to the db. It doesn't mean
+        // the network is invalid.
+        if (netId == INVALID_NET_ID) {
+            Toast.makeText(context, R.string.wifi_failed_connect_message,
+                    Toast.LENGTH_SHORT).show();
+        } else {
+            wifiManager.enableNetwork(netId, true);
+        }
+        return netId;
+    }
+
+    private static boolean isHexString(String password) {
+        return HEX_PATTERN.matcher(password).matches();
+    }
 }
diff --git a/src/com/android/car/settings/wifi/details/WifiDetailsBasePreferenceController.java b/src/com/android/car/settings/wifi/details/WifiDetailsBasePreferenceController.java
new file mode 100644
index 0000000..8c23ed3
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiDetailsBasePreferenceController.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.wifi.WifiUtil;
+import com.android.settingslib.wifi.AccessPoint;
+
+/**
+ * Controller for logic pertaining to displaying Wifi information. Only available when wifi is
+ * active.
+ *
+ * <p>Subclasses should use
+ * {@link com.android.car.settings.common.PreferenceController#updateState(Preference)} to render UI
+ * with latest info if desired.
+ *
+ * @param <V> the upper bound on the type of {@link Preference} on which the controller
+ *            expects to operate.
+ */
+public abstract class WifiDetailsBasePreferenceController<V extends Preference> extends
+        PreferenceController<V> implements WifiInfoProvider.Listener {
+
+    private AccessPoint mAccessPoint;
+    private WifiInfoProvider mWifiInfoProvider;
+
+    public WifiDetailsBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /**
+     * Sets all parameters for the controller to run, need to get called as early as possible.
+     */
+    public WifiDetailsBasePreferenceController init(
+            AccessPoint accessPoint, WifiInfoProvider wifiInfoProvider) {
+        mAccessPoint = accessPoint;
+        mWifiInfoProvider = wifiInfoProvider;
+        return this;
+    }
+
+    /** Gets the access poing that this controller was initialized with. */
+    protected AccessPoint getAccessPoint() {
+        return mAccessPoint;
+    }
+
+    /** Gets the wifi info provider that this controller was initialized with. */
+    protected WifiInfoProvider getWifiInfoProvider() {
+        return mWifiInfoProvider;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mWifiInfoProvider.addListener(this);
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mWifiInfoProvider.removeListener(this);
+    }
+
+    @Override
+    public void onWifiConfigurationChanged(WifiConfiguration wifiConfiguration,
+            NetworkInfo networkInfo, WifiInfo wifiInfo) {
+        refreshUi();
+    }
+
+    @Override
+    public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+        refreshUi();
+    }
+
+    @Override
+    public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+    }
+
+    @Override
+    public void onWifiChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) {
+        getPreference().setEnabled(true);
+        refreshUi();
+    }
+
+    @Override
+    public void onLost(Network network) {
+        getPreference().setEnabled(false);
+        refreshUi();
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        if (!WifiUtil.isWifiAvailable(getContext())) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        return getAccessPoint().isActive() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiDetailsFragment.java b/src/com/android/car/settings/wifi/details/WifiDetailsFragment.java
new file mode 100644
index 0000000..0e49c38
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiDetailsFragment.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.car.settings.wifi.WifiUtil;
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows details about a wifi network, including actions related to the network,
+ * e.g. ignore, disconnect, etc. The intent should include information about
+ * access point, use that to render UI, e.g. show SSID etc.
+ */
+public class WifiDetailsFragment extends SettingsFragment
+        implements WifiInfoProvider.Listener {
+    private static final String EXTRA_AP_STATE = "extra_ap_state";
+    private static final Logger LOG = new Logger(WifiDetailsFragment.class);
+
+    private WifiManager mWifiManager;
+    private AccessPoint mAccessPoint;
+    private Button mForgetButton;
+    private Button mConnectButton;
+    private List<WifiDetailsBasePreferenceController> mControllers = new ArrayList<>();
+
+    private WifiInfoProvider mWifiInfoProvider;
+
+    private class ActionFailListener implements WifiManager.ActionListener {
+        @StringRes
+        private final int mMessageResId;
+
+        ActionFailListener(@StringRes int messageResId) {
+            mMessageResId = messageResId;
+        }
+
+        @Override
+        public void onSuccess() {
+        }
+
+        @Override
+        public void onFailure(int reason) {
+            Toast.makeText(getContext(), mMessageResId, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /**
+     * Gets an instance of this class.
+     */
+    public static WifiDetailsFragment getInstance(AccessPoint accessPoint) {
+        WifiDetailsFragment wifiDetailsFragment = new WifiDetailsFragment();
+        Bundle bundle = new Bundle();
+        Bundle accessPointState = new Bundle();
+        accessPoint.saveWifiState(accessPointState);
+        bundle.putBundle(EXTRA_AP_STATE, accessPointState);
+        wifiDetailsFragment.setArguments(bundle);
+        return wifiDetailsFragment;
+    }
+
+    @Override
+    @LayoutRes
+    protected int getActionBarLayoutId() {
+        return R.layout.action_bar_with_button;
+    }
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_detail_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mAccessPoint = new AccessPoint(getContext(), getArguments().getBundle(EXTRA_AP_STATE));
+        mWifiManager = context.getSystemService(WifiManager.class);
+        LOG.d("Creating WifiInfoProvider for " + mAccessPoint);
+        if (mWifiInfoProvider == null) {
+            mWifiInfoProvider = new WifiInfoProviderImpl(getContext(), mAccessPoint);
+        }
+        getLifecycle().addObserver(mWifiInfoProvider);
+
+        LOG.d("Creating WifiInfoProvider.Listeners.");
+        mControllers.add(use(
+                WifiSignalStrengthPreferenceController.class, R.string.pk_wifi_signal_strength)
+                .init(mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiFrequencyPreferenceController.class, R.string.pk_wifi_frequency)
+                .init(mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiSecurityPreferenceController.class, R.string.pk_wifi_security)
+                .init(mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiMacAddressPreferenceController.class, R.string.pk_wifi_mac_address)
+                .init(mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiIpAddressPreferenceController.class, R.string.pk_wifi_ip).init(
+                mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiGatewayPreferenceController.class, R.string.pk_wifi_gateway).init(
+                mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiSubnetPreferenceController.class, R.string.pk_wifi_subnet_mask)
+                .init(mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiDnsPreferenceController.class, R.string.pk_wifi_dns).init(
+                mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiLinkSpeedPreferenceController.class, R.string.pk_wifi_link_speed)
+                .init(mAccessPoint, mWifiInfoProvider));
+        mControllers.add(use(WifiIpv6AddressPreferenceController.class, R.string.pk_wifi_ipv6).init(
+                mAccessPoint, mWifiInfoProvider));
+        LOG.d("Done init.");
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        ((TextView) getActivity().findViewById(R.id.title)).setText(mAccessPoint.getSsid());
+
+        mConnectButton = getActivity().findViewById(R.id.action_button2);
+        mConnectButton.setVisibility(View.VISIBLE);
+        mConnectButton.setText(R.string.wifi_setup_connect);
+        mConnectButton.setOnClickListener(v -> {
+            mWifiManager.connect(mAccessPoint.getConfig(),
+                    new ActionFailListener(R.string.wifi_failed_connect_message));
+            goBack();
+        });
+        mForgetButton = getActivity().findViewById(R.id.action_button1);
+        mForgetButton.setText(R.string.forget);
+        mForgetButton.setOnClickListener(v -> {
+            forget();
+            goBack();
+        });
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mWifiInfoProvider.addListener(this);
+        updateUi();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mWifiInfoProvider.removeListener(this);
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        getLifecycle().removeObserver(mWifiInfoProvider);
+    }
+
+    @Override
+    public void onWifiChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) {
+        updateUi();
+    }
+
+    @Override
+    public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
+        updateUi();
+    }
+
+    @Override
+    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
+        updateUi();
+    }
+
+    @Override
+    public void onLost(Network network) {
+        updateUi();
+    }
+
+    @Override
+    public void onWifiConfigurationChanged(WifiConfiguration wifiConfiguration,
+            NetworkInfo networkInfo, WifiInfo wifiInfo) {
+        updateUi();
+    }
+
+    private void updateUi() {
+        LOG.d("updating.");
+        // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
+        // callbacks. mNetwork doesn't change except in onResume.
+        if (mWifiInfoProvider.getNetwork() == null
+                || mWifiInfoProvider.getNetworkInfo() == null
+                || mWifiInfoProvider.getWifiInfo() == null) {
+            LOG.d("WIFI not available.");
+            return;
+        }
+
+        mConnectButton.setVisibility(needConnect() ? View.VISIBLE : View.INVISIBLE);
+        mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE);
+        LOG.d("updated.");
+    }
+
+    private boolean needConnect() {
+        return mAccessPoint.isSaved() && !mAccessPoint.isActive();
+    }
+
+    /**
+     * Returns whether the network represented by this fragment can be forgotten.
+     */
+    private boolean canForgetNetwork() {
+        return (mWifiInfoProvider.getWifiInfo() != null
+                && mWifiInfoProvider.getWifiInfo().isEphemeral()) || canModifyNetwork();
+    }
+
+    /**
+     * Returns whether the network represented by this preference can be modified.
+     */
+    private boolean canModifyNetwork() {
+        WifiConfiguration wifiConfig = mWifiInfoProvider.getNetworkConfiguration();
+        LOG.d("wifiConfig is: " + wifiConfig);
+        return wifiConfig != null && !WifiUtil.isNetworkLockedDown(getContext(), wifiConfig);
+    }
+
+    private void forget() {
+        if (!mAccessPoint.isSaved()) {
+            if (mAccessPoint.getNetworkInfo() != null
+                    && mAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
+                // Network is active but has no network ID - must be ephemeral.
+                mWifiManager.disableEphemeralNetwork(
+                        AccessPoint.convertToQuotedString(mAccessPoint.getSsidStr()));
+            } else {
+                // Should not happen, but a monkey seems to trigger it
+                LOG.e("Failed to forget invalid network " + mAccessPoint.getConfig());
+                return;
+            }
+        } else {
+            mWifiManager.forget(mAccessPoint.getConfig().networkId,
+                    new ActionFailListener(R.string.wifi_failed_forget_message));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiDetailsPreference.java b/src/com/android/car/settings/wifi/details/WifiDetailsPreference.java
new file mode 100644
index 0000000..4e723ba
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiDetailsPreference.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.R;
+
+/**
+ * A Preference to be used with the Wifi Network Detail Fragment that allows a summary text to be
+ * set inside the widget resource
+ */
+public class WifiDetailsPreference extends Preference {
+    private String mDetailText;
+
+    public WifiDetailsPreference(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+        setWidgetLayoutResource(R.layout.summary_preference_widget);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    WifiDetailsPreference(Context context) {
+        super(context);
+    }
+
+    /**
+     * Sets the detail text.
+     */
+    public void setDetailText(String text) {
+        if (TextUtils.equals(mDetailText, text)) {
+            return;
+        }
+        mDetailText = text;
+        notifyChanged();
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+        TextView textView = ((TextView) view.findViewById(R.id.widget_summary));
+        textView.setText(mDetailText);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    String getDetailText() {
+        return mDetailText;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiDnsPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiDnsPreferenceController.java
new file mode 100644
index 0000000..2a508fd
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiDnsPreferenceController.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+
+import java.net.InetAddress;
+import java.util.stream.Collectors;
+
+/**
+ * Shows info about Wifi DNS info.
+ */
+public class WifiDnsPreferenceController extends
+        WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+
+    public WifiDnsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        String dnsServers = getWifiInfoProvider().getLinkProperties().getDnsServers().stream()
+                .map(InetAddress::getHostAddress)
+                .collect(Collectors.joining(System.lineSeparator()));
+
+        if (dnsServers == null) {
+            preference.setVisible(false);
+        } else {
+            preference.setDetailText(dnsServers);
+            preference.setVisible(true);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiFrequencyPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiFrequencyPreferenceController.java
new file mode 100644
index 0000000..3e5f7c6
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiFrequencyPreferenceController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.wifi.AccessPoint;
+
+/**
+ * Shows frequency info about the Wifi connection.
+ */
+public class WifiFrequencyPreferenceController extends
+        WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+    private static final Logger LOG = new Logger(WifiFrequencyPreferenceController.class);
+
+    public WifiFrequencyPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        int frequency = getWifiInfoProvider().getWifiInfo().getFrequency();
+        String band = null;
+        if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
+                && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
+            band = getContext().getResources().getString(R.string.wifi_band_24ghz);
+        } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
+                && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
+            band = getContext().getResources().getString(R.string.wifi_band_5ghz);
+        } else {
+            LOG.e("Unexpected frequency " + frequency);
+        }
+        preference.setDetailText(band);
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiGatewayPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiGatewayPreferenceController.java
new file mode 100644
index 0000000..23bef4f
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiGatewayPreferenceController.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.RouteInfo;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Shows info about Wifi Gateway info.
+ */
+public class WifiGatewayPreferenceController extends
+        WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+
+    public WifiGatewayPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        String gateway = null;
+        for (RouteInfo routeInfo : getWifiInfoProvider().getLinkProperties().getRoutes()) {
+            if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
+                gateway = routeInfo.getGateway().getHostAddress();
+                break;
+            }
+        }
+
+        if (gateway == null) {
+            preference.setVisible(false);
+        } else {
+            preference.setDetailText(gateway);
+            preference.setVisible(true);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiInfoProvider.java b/src/com/android/car/settings/wifi/details/WifiInfoProvider.java
new file mode 100644
index 0000000..3008ebe
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiInfoProvider.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+
+import androidx.lifecycle.LifecycleObserver;
+
+/**
+ * Provides Wifi related info.
+ */
+public interface WifiInfoProvider extends LifecycleObserver {
+    /**
+     * Observers of Wifi info changes.
+     */
+    public interface Listener {
+        /**
+         * Called when NetworkInfo and/or WifiInfo is changed.
+         */
+        void onWifiChanged(NetworkInfo networkInfo, WifiInfo wifiInfo);
+
+        /**
+         * Called when network is lost.
+         */
+        void onLost(Network network);
+
+        /**
+         * Called when NetworkCapabilities changed.
+         */
+        void onCapabilitiesChanged(Network network, NetworkCapabilities nc);
+
+        /**
+         * Called when WifiConfiguration changed.
+         */
+        void onWifiConfigurationChanged(WifiConfiguration wifiConfiguration,
+                NetworkInfo networkInfo, WifiInfo wifiInfo);
+
+        /**
+         * Called when LinkProperties changed.
+         */
+        void onLinkPropertiesChanged(Network network, LinkProperties lp);
+    }
+
+    /**
+     * Adds the listener.
+     */
+    void addListener(Listener listener);
+
+    /**
+     * Removes the listener.
+     */
+    void removeListener(Listener listener);
+
+    /**
+     * Removes all listeners.
+     */
+    void clearListeners();
+
+    /**
+     * Getter for NetworkInfo
+     */
+    NetworkInfo getNetworkInfo();
+
+    /**
+     * Getter for WifiInfo
+     */
+    WifiInfo getWifiInfo();
+
+    /**
+     * Getter for Network
+     */
+    Network getNetwork();
+
+    /**
+     * Getter for NetworkCapabilities.
+     */
+    NetworkCapabilities getNetworkCapabilities();
+
+    /**
+     * Getter for NetworkConfiguration.
+     */
+    WifiConfiguration getNetworkConfiguration();
+
+    /**
+     * Getter for LinkProperties.
+     */
+    LinkProperties getLinkProperties();
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiInfoProviderImpl.java b/src/com/android/car/settings/wifi/details/WifiInfoProviderImpl.java
new file mode 100644
index 0000000..3ba3dc2
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiInfoProviderImpl.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.OnLifecycleEvent;
+
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Provides Wifi related info.
+ */
+public class WifiInfoProviderImpl implements WifiInfoProvider {
+    private static final Logger LOG = new Logger(WifiInfoProviderImpl.class);
+
+    private final AccessPoint mAccessPoint;
+    private final Context mContext;
+    private final Set<Listener> mListeners = new HashSet<>();
+    private final WifiManager mWifiManager;
+    private final IntentFilter mFilter;
+    private final ConnectivityManager mConnectivityManager;
+    private final Handler mHandler;
+    private final Network mNetwork;
+
+    private LinkProperties mLinkProperties;
+    private NetworkCapabilities mNetworkCapabilities;
+    private WifiConfiguration mWifiConfig;
+    private NetworkInfo mNetworkInfo;
+    private WifiInfo mWifiInfo;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
+                    LOG.d("Wifi Config changed.");
+                    if (!intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED,
+                            false /* defaultValue */)) {
+                        // only one network changed
+                        WifiConfiguration wifiConfiguration = intent
+                                .getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
+                        if (mAccessPoint.matches(wifiConfiguration)) {
+                            mWifiConfig = wifiConfiguration;
+                        }
+                    }
+                    updateInfo();
+                    for (Listener listener : getListenersCopy()) {
+                        listener.onWifiConfigurationChanged(mWifiConfig, mNetworkInfo, mWifiInfo);
+                    }
+                    break;
+                case WifiManager.NETWORK_STATE_CHANGED_ACTION:
+                case WifiManager.RSSI_CHANGED_ACTION:
+                    LOG.d("wifi changed.");
+                    updateInfo();
+                    for (Listener listener : getListenersCopy()) {
+                        listener.onWifiChanged(mNetworkInfo, mWifiInfo);
+                    }
+                    break;
+            }
+        }
+    };
+
+    private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
+            .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
+
+    // Must be run on the UI thread since it directly manipulates UI state.
+    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+            if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
+                mLinkProperties = lp;
+                for (Listener listener : getListenersCopy()) {
+                    listener.onLinkPropertiesChanged(mNetwork, mLinkProperties);
+                }
+            }
+        }
+
+        private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
+            // If this is the first time we get NetworkCapabilities, report that something changed.
+            if (mNetworkCapabilities == null) return true;
+
+            // nc can never be null, see ConnectivityService#callCallbackForRequest.
+            return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
+        }
+
+        @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+            // If the network just validated or lost Internet access, refresh network state.
+            if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
+                if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED)
+                        || hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
+                    mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
+                }
+                mNetworkCapabilities = nc;
+                for (Listener listener : getListenersCopy()) {
+                    listener.onCapabilitiesChanged(mNetwork, mNetworkCapabilities);
+                }
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            if (network.equals(mNetwork)) {
+                for (Listener listener : getListenersCopy()) {
+                    listener.onLost(mNetwork);
+                }
+            }
+        }
+    };
+
+    WifiInfoProviderImpl(Context context, AccessPoint accessPoint) {
+        mContext = context;
+        mAccessPoint = accessPoint;
+        mHandler = new Handler(Looper.getMainLooper());
+        mConnectivityManager =
+                mContext.getSystemService(ConnectivityManager.class);
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mWifiConfig = mAccessPoint.getConfig();
+        mNetwork = mWifiManager.getCurrentNetwork();
+        mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
+        mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
+        updateInfo();
+        mFilter = new IntentFilter();
+        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+        mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+    }
+
+    @Override
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void clearListeners() {
+        mListeners.clear();
+    }
+
+    /**
+     * Called when Lifecycle owner onStart is called.
+     */
+    @OnLifecycleEvent(Lifecycle.Event.ON_START)
+    public void onStart() {
+        LOG.d("onStart");
+        mContext.registerReceiver(mReceiver, mFilter);
+        mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
+                mHandler);
+        for (Listener listener : getListenersCopy()) {
+            listener.onWifiChanged(mNetworkInfo, mWifiInfo);
+        }
+        LOG.d("Done onStart");
+    }
+
+    /**
+     * Called when Lifecycle owner onStop is called.
+     */
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    public void onStop() {
+        LOG.d("onStop");
+        mContext.unregisterReceiver(mReceiver);
+        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        LOG.d("done onStop");
+    }
+
+    @Override
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
+    }
+
+    @Override
+    public WifiInfo getWifiInfo() {
+        return mWifiInfo;
+    }
+
+    @Override
+    public Network getNetwork() {
+        return mNetwork;
+    }
+
+    @Override
+    public NetworkCapabilities getNetworkCapabilities() {
+        return mNetworkCapabilities;
+    }
+
+    @Override
+    public WifiConfiguration getNetworkConfiguration() {
+        return mWifiConfig;
+    }
+
+    @Override
+    public LinkProperties getLinkProperties() {
+        return mLinkProperties;
+    }
+
+    private Collection<Listener> getListenersCopy() {
+        return Collections.unmodifiableCollection(mListeners);
+    }
+
+    private void updateInfo() {
+        // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
+        // callbacks.
+        mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
+        mWifiInfo = mWifiManager.getConnectionInfo();
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiIpAddressPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiIpAddressPreferenceController.java
new file mode 100644
index 0000000..3b23bf0
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiIpAddressPreferenceController.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.LinkAddress;
+
+import com.android.car.settings.common.FragmentController;
+
+import java.net.Inet4Address;
+
+/**
+ * Shows info about Wifi IP address.
+ */
+public class WifiIpAddressPreferenceController extends
+        WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+
+    public WifiIpAddressPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        String ipv4Address = null;
+
+        for (LinkAddress addr : getWifiInfoProvider().getLinkProperties().getLinkAddresses()) {
+            if (addr.getAddress() instanceof Inet4Address) {
+                ipv4Address = addr.getAddress().getHostAddress();
+            }
+        }
+
+        if (ipv4Address == null) {
+            preference.setVisible(false);
+        } else {
+            preference.setDetailText(ipv4Address);
+            preference.setVisible(true);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiIpv6AddressPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiIpv6AddressPreferenceController.java
new file mode 100644
index 0000000..28b85e5
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiIpv6AddressPreferenceController.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.LinkAddress;
+
+import androidx.core.text.BidiFormatter;
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+
+import java.net.Inet6Address;
+import java.util.StringJoiner;
+
+/**
+ * Shows info about Wifi IPv6 address.
+ */
+public class WifiIpv6AddressPreferenceController extends
+        WifiDetailsBasePreferenceController<Preference> {
+
+    public WifiIpv6AddressPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        StringJoiner ipv6Addresses = new StringJoiner(System.lineSeparator());
+
+        for (LinkAddress addr : getWifiInfoProvider().getLinkProperties().getLinkAddresses()) {
+            if (addr.getAddress() instanceof Inet6Address) {
+                ipv6Addresses.add(addr.getAddress().getHostAddress());
+            }
+        }
+
+        if (ipv6Addresses.length() > 0) {
+            preference.setSummary(
+                    BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
+            preference.setVisible(true);
+        } else {
+            preference.setVisible(false);
+        }
+    }
+}
+
diff --git a/src/com/android/car/settings/wifi/details/WifiLinkSpeedPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiLinkSpeedPreferenceController.java
new file mode 100644
index 0000000..6818e4b
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiLinkSpeedPreferenceController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Shows info about Wifi link speed info.
+ */
+public class WifiLinkSpeedPreferenceController extends
+        WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+
+    public WifiLinkSpeedPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        int linkSpeedMbps = getWifiInfoProvider().getWifiInfo().getLinkSpeed();
+        preference.setVisible(linkSpeedMbps >= 0);
+        preference.setDetailText(getContext().getString(
+                R.string.link_speed, getWifiInfoProvider().getWifiInfo().getLinkSpeed()));
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiMacAddressPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiMacAddressPreferenceController.java
new file mode 100644
index 0000000..e57847f
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiMacAddressPreferenceController.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Shows info about Wifi MAC address.
+ */
+public class WifiMacAddressPreferenceController extends
+        WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+
+    public WifiMacAddressPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        preference.setDetailText(getWifiInfoProvider().getWifiInfo().getMacAddress());
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiSecurityPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiSecurityPreferenceController.java
new file mode 100644
index 0000000..f36344c
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiSecurityPreferenceController.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Shows security info about the Wifi connection.
+ */
+public class WifiSecurityPreferenceController extends
+        WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+    public WifiSecurityPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        preference.setDetailText(getAccessPoint().getSecurityString(false /* concise */));
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiSignalStrengthPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiSignalStrengthPreferenceController.java
new file mode 100644
index 0000000..e1fe311
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiSignalStrengthPreferenceController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+
+/**
+ * Shows info about Wifi signal strength.
+ */
+public class WifiSignalStrengthPreferenceController
+        extends WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+
+    private int mRssiSignalLevel = -1;
+    private String[] mSignalStr;
+
+    public WifiSignalStrengthPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mSignalStr = context.getResources().getStringArray(R.array.wifi_signals);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        mRssiSignalLevel = getAccessPoint().getLevel();
+        preference.setDetailText(mSignalStr[mRssiSignalLevel]);
+    }
+}
diff --git a/src/com/android/car/settings/wifi/details/WifiSubnetPreferenceController.java b/src/com/android/car/settings/wifi/details/WifiSubnetPreferenceController.java
new file mode 100644
index 0000000..d7aecdd
--- /dev/null
+++ b/src/com/android/car/settings/wifi/details/WifiSubnetPreferenceController.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.settings.wifi.details;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+
+import com.android.car.settings.common.FragmentController;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Shows info about Wifi subnet.
+ */
+public class WifiSubnetPreferenceController extends
+        WifiDetailsBasePreferenceController<WifiDetailsPreference> {
+
+    public WifiSubnetPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<WifiDetailsPreference> getPreferenceType() {
+        return WifiDetailsPreference.class;
+    }
+
+    @Override
+    protected void updateState(WifiDetailsPreference preference) {
+        String subnet = null;
+
+        for (LinkAddress addr : getWifiInfoProvider().getLinkProperties().getLinkAddresses()) {
+            if (addr.getAddress() instanceof Inet4Address) {
+                subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
+            }
+        }
+
+        if (subnet == null) {
+            preference.setVisible(false);
+        } else {
+            preference.setDetailText(subnet);
+            preference.setVisible(true);
+        }
+    }
+
+    private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
+        try {
+            InetAddress all = InetAddress.getByAddress(
+                    new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
+            return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/wifi/preferences/CellularFallbackTogglePreferenceController.java b/src/com/android/car/settings/wifi/preferences/CellularFallbackTogglePreferenceController.java
new file mode 100644
index 0000000..9d3a764
--- /dev/null
+++ b/src/com/android/car/settings/wifi/preferences/CellularFallbackTogglePreferenceController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi.preferences;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/** Business logic to enable falling back to cellular data when wifi is poor. */
+public class CellularFallbackTogglePreferenceController extends
+        PreferenceController<TwoStatePreference> {
+
+    private static final String ENABLED = "1";
+    private static final String DISABLED = null;
+
+    public CellularFallbackTogglePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return avoidBadWifiConfigEnabled() ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(cellularFallbackEnabled());
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
+        boolean enableCellularFallback = (Boolean) newValue;
+        Settings.Global.putString(getContext().getContentResolver(),
+                Settings.Global.NETWORK_AVOID_BAD_WIFI,
+                enableCellularFallback ? ENABLED : DISABLED);
+        return true;
+    }
+
+    /**
+     * See {@link Settings.Global#NETWORK_AVOID_BAD_WIFI} for description of this configuration. In
+     * short, if the device is configured to avoid bad wifi, the option to fallback to cellular is
+     * not shown.
+     */
+    private boolean avoidBadWifiConfigEnabled() {
+        return getContext().getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 1;
+    }
+
+    private boolean cellularFallbackEnabled() {
+        return TextUtils.equals(ENABLED,
+                Settings.Global.getString(getContext().getContentResolver(),
+                        Settings.Global.NETWORK_AVOID_BAD_WIFI));
+    }
+}
diff --git a/src/com/android/car/settings/wifi/preferences/ConfirmEnableWifiScanningDialogFragment.java b/src/com/android/car/settings/wifi/preferences/ConfirmEnableWifiScanningDialogFragment.java
new file mode 100644
index 0000000..168c99b
--- /dev/null
+++ b/src/com/android/car/settings/wifi/preferences/ConfirmEnableWifiScanningDialogFragment.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi.preferences;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import androidx.fragment.app.DialogFragment;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.HelpUtils;
+
+/** Dialog to request enabling of wifi scanning when user tries to enable auto wifi wakeup. */
+public class ConfirmEnableWifiScanningDialogFragment extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    public static final String TAG = "ConfirmEnableWifiScanningDialogFragment";
+    private static final Logger LOG = new Logger(ConfirmEnableWifiScanningDialogFragment.class);
+
+    private WifiScanningEnabledListener mListener;
+
+    /** Sets the wifi scanning enabled listener. */
+    public void setWifiScanningEnabledListener(WifiScanningEnabledListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
+                .setTitle(R.string.wifi_settings_scanning_required_title)
+                .setPositiveButton(R.string.wifi_settings_scanning_required_turn_on, this)
+                .setNegativeButton(R.string.cancel, null);
+
+        // Only show "learn more" if there is a help page to show
+        if (!TextUtils.isEmpty(getContext().getString(R.string.help_uri_wifi_scanning_required))) {
+            builder.setNeutralButton(R.string.learn_more, this);
+        }
+
+        return builder.create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case DialogInterface.BUTTON_POSITIVE:
+                if (mListener != null) {
+                    mListener.onWifiScanningEnabled();
+                }
+                break;
+            case DialogInterface.BUTTON_NEUTRAL:
+                openHelpPage();
+                break;
+            case DialogInterface.BUTTON_NEGATIVE:
+            default:
+                // do nothing
+        }
+    }
+
+    private void openHelpPage() {
+        Intent intent = HelpUtils.getHelpIntent(getContext(),
+                getContext().getString(R.string.help_uri_wifi_scanning_required),
+                getContext().getClass().getName());
+        if (intent != null) {
+            try {
+                startActivity(intent);
+            } catch (ActivityNotFoundException e) {
+                LOG.e("Activity was not found for intent, " + intent.toString());
+            }
+        }
+    }
+
+    /** Listener for when the dialog is confirmed and the wifi scanning is enabled. */
+    public interface WifiScanningEnabledListener {
+        /** Actions to take when wifi scanning is enabled. */
+        void onWifiScanningEnabled();
+    }
+}
diff --git a/src/com/android/car/settings/wifi/preferences/WifiPreferencesFragment.java b/src/com/android/car/settings/wifi/preferences/WifiPreferencesFragment.java
new file mode 100644
index 0000000..5a73fef
--- /dev/null
+++ b/src/com/android/car/settings/wifi/preferences/WifiPreferencesFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi.preferences;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Screen to control wifi specific configurations. */
+public class WifiPreferencesFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_preferences_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/wifi/preferences/WifiWakeupTogglePreferenceController.java b/src/com/android/car/settings/wifi/preferences/WifiWakeupTogglePreferenceController.java
new file mode 100644
index 0000000..363037f
--- /dev/null
+++ b/src/com/android/car/settings/wifi/preferences/WifiWakeupTogglePreferenceController.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi.preferences;
+
+import android.app.Service;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.location.LocationManager;
+import android.provider.Settings;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.URLSpan;
+import android.widget.Toast;
+
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+/** Business logic to allow auto-enabling of wifi near saved networks. */
+public class WifiWakeupTogglePreferenceController extends
+        PreferenceController<TwoStatePreference> implements
+        ConfirmEnableWifiScanningDialogFragment.WifiScanningEnabledListener {
+
+    private static final Logger LOG = new Logger(WifiWakeupTogglePreferenceController.class);
+    private final LocationManager mLocationManager;
+
+    public WifiWakeupTogglePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mLocationManager = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE);
+    }
+
+    @Override
+    protected Class<TwoStatePreference> getPreferenceType() {
+        return TwoStatePreference.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        ConfirmEnableWifiScanningDialogFragment dialogFragment =
+                (ConfirmEnableWifiScanningDialogFragment) getFragmentController().findDialogByTag(
+                        ConfirmEnableWifiScanningDialogFragment.TAG);
+        if (dialogFragment != null) {
+            dialogFragment.setWifiScanningEnabledListener(this);
+        }
+    }
+
+    @Override
+    protected void updateState(TwoStatePreference preference) {
+        preference.setChecked(getWifiWakeupEnabled()
+                && getWifiScanningEnabled()
+                && mLocationManager.isLocationEnabled());
+        if (!mLocationManager.isLocationEnabled()) {
+            preference.setSummary(getNoLocationSummary());
+        } else {
+            preference.setSummary(R.string.wifi_wakeup_summary);
+        }
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(TwoStatePreference preference) {
+        if (!mLocationManager.isLocationEnabled()) {
+            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+            getContext().startActivity(intent);
+        } else if (getWifiWakeupEnabled()) {
+            setWifiWakeupEnabled(false);
+        } else if (!getWifiScanningEnabled()) {
+            showScanningDialog();
+        } else {
+            setWifiWakeupEnabled(true);
+        }
+
+        refreshUi();
+        return true;
+    }
+
+    private boolean getWifiWakeupEnabled() {
+        return Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
+    }
+
+    private void setWifiWakeupEnabled(boolean enabled) {
+        Settings.Global.putInt(getContext().getContentResolver(),
+                Settings.Global.WIFI_WAKEUP_ENABLED,
+                enabled ? 1 : 0);
+    }
+
+    private boolean getWifiScanningEnabled() {
+        return Settings.Global.getInt(getContext().getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
+    }
+
+    private void enableWifiScanning() {
+        Settings.Global.putInt(getContext().getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1);
+        Toast.makeText(
+                getContext(),
+                getContext().getString(R.string.wifi_settings_scanning_required_enabled),
+                Toast.LENGTH_SHORT).show();
+    }
+
+    private CharSequence getNoLocationSummary() {
+        String highlightedWord = "link";
+        CharSequence locationText = getContext()
+                .getText(R.string.wifi_wakeup_summary_no_location);
+
+        SpannableString msg = new SpannableString(locationText);
+        int startIndex = locationText.toString().indexOf(highlightedWord);
+        if (startIndex < 0) {
+            LOG.e("Cannot create URL span");
+            return locationText;
+        }
+        msg.setSpan(new URLSpan((String) null), startIndex, startIndex + highlightedWord.length(),
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        return msg;
+    }
+
+    private void showScanningDialog() {
+        ConfirmEnableWifiScanningDialogFragment dialogFragment =
+                new ConfirmEnableWifiScanningDialogFragment();
+        dialogFragment.setWifiScanningEnabledListener(this);
+        getFragmentController().showDialog(dialogFragment,
+                ConfirmEnableWifiScanningDialogFragment.TAG);
+    }
+
+    @Override
+    public void onWifiScanningEnabled() {
+        enableWifiScanning();
+        if (mLocationManager.isLocationEnabled()) {
+            setWifiWakeupEnabled(true);
+        }
+        refreshUi();
+    }
+}
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index 28d3fd4..17130f0 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -1,5 +1,5 @@
 #############################################
-# Car Settings Robolectric test target. #
+# Car Settings Robolectric test target.     #
 #############################################
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
@@ -16,16 +16,17 @@
     robolectric_android-all-stub \
     Robolectric_all-target \
     mockito-robolectric-prebuilt \
+    testng \
     truth-prebuilt
 
-LOCAL_INSTRUMENTATION_FOR := CarSettings
+LOCAL_INSTRUMENTATION_FOR := CarSettingsForTesting
 
 LOCAL_MODULE_TAGS := optional
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 #############################################################
-# Car Settings runner target to run the previous target. #
+# Car Settings runner target to run the previous target.    #
 #############################################################
 include $(CLEAR_VARS)
 
@@ -37,9 +38,10 @@
     robolectric_android-all-stub \
     Robolectric_all-target \
     mockito-robolectric-prebuilt \
+    testng \
     truth-prebuilt
 
-LOCAL_TEST_PACKAGE := CarSettings
+LOCAL_TEST_PACKAGE := CarSettingsForTesting
 
 LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
 
diff --git a/tests/robotests/AndroidManifest.xml b/tests/robotests/AndroidManifest.xml
index 77fd39c..efe1f69 100644
--- a/tests/robotests/AndroidManifest.xml
+++ b/tests/robotests/AndroidManifest.xml
@@ -19,5 +19,4 @@
           coreApp="true"
           package="com.android.car.settings.robotests">
     <application/>
-
 </manifest>
diff --git a/tests/robotests/res/drawable/test_icon.png b/tests/robotests/res/drawable/test_icon.png
new file mode 100644
index 0000000..a1cf83e
--- /dev/null
+++ b/tests/robotests/res/drawable/test_icon.png
Binary files differ
diff --git a/tests/robotests/res/values/arrays.xml b/tests/robotests/res/values/arrays.xml
new file mode 100644
index 0000000..0b92530
--- /dev/null
+++ b/tests/robotests/res/values/arrays.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<resources>
+    <string-array name="entries" translatable="false">
+        <item>Choose me!</item>
+        <item>No, me!</item>
+        <item>What about me?!</item>
+    </string-array>
+
+    <string-array name="entry_values" translatable="false">
+        <item>alpha</item>
+        <item>beta</item>
+        <item>charlie</item>
+    </string-array>
+</resources>
diff --git a/tests/robotests/res/values/config.xml b/tests/robotests/res/values/config.xml
new file mode 100644
index 0000000..0b52270
--- /dev/null
+++ b/tests/robotests/res/values/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<resources>
+    <!-- replace with simple fragment for testing purpose . -->
+    <string name="config_settings_hierarchy_root_fragment" translatable="false">com.android.car.settings.testutils.DummyFragment</string>
+    <!-- explicitly set to unset string for testing purpose -->
+    <string name="config_defaultAssistantComponentName" translatable="false">#+UNSET</string>
+</resources>
diff --git a/tests/robotests/res/values/internal_resources.xml b/tests/robotests/res/values/internal_resources.xml
new file mode 100644
index 0000000..90c541e
--- /dev/null
+++ b/tests/robotests/res/values/internal_resources.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<resources>
+    <bool name="config_automatic_brightness_available">true</bool>
+    <integer name="config_networkAvoidBadWifi">0</integer>
+
+    <!-- Progress bar preference resources -->
+    <color name="config_progress_background_tint">@android:color/white</color>
+    <color name="white_disabled_material">@android:color/white</color>
+</resources>
diff --git a/tests/robotests/res/values/preference_keys.xml b/tests/robotests/res/values/preference_keys.xml
new file mode 100644
index 0000000..f76feae
--- /dev/null
+++ b/tests/robotests/res/values/preference_keys.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<!-- Namespaced with "tpk" (Test Preference Key) to avoid conflicts. -->
+<resources>
+    <!-- SettingsFragmentTest -->
+    <string name="tpk_fake_controller" translatable="false">fake_controller</string>
+    <string name="tpk_edit_text_preference" translatable="false">edit_text_preference</string>
+    <string name="tpk_list_preference" translatable="false">list_preference</string>
+
+    <string name="tpk_two_action_preference" translatable="false">two_action_preference</string>
+</resources>
diff --git a/tests/robotests/res/values/strings.xml b/tests/robotests/res/values/strings.xml
new file mode 100644
index 0000000..aed9f9e
--- /dev/null
+++ b/tests/robotests/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="test_volume_call" translatable="false">Call Volume</string>
+    <string name="test_volume_music" translatable="false">Music Volume</string>
+
+    <!-- AccountListPreferenceControllerTest and AddAccountPreferenceControllerTest -->
+    <string name="account_type1_label" translatable="false">Type 1</string>
+    <string name="account_type2_label" translatable="false">Type 2</string>
+    <string name="account_type3_label" translatable="false">Type 3</string>
+
+    <string name="bt_rename_dialog_title" translatable="false">Rename Title</string>
+    <string name="bt_profile_name" translatable="false">Profile Name</string>
+
+    <string name="test_positive_button_label" translatable="false">Positive Button</string>
+    <string name="test_negative_button_label" translatable="false">Negative Button</string>
+
+    <string name="fake_title" translatable="false">fake_title</string>
+    <string name="fake_summary" translatable="false">fake_summary</string>
+    <string name="fake_category" translatable="false">fake_category</string>
+</resources>
diff --git a/tests/robotests/res/xml/preference_controller_list_helper_fail_invalid_controller.xml b/tests/robotests/res/xml/preference_controller_list_helper_fail_invalid_controller.xml
new file mode 100644
index 0000000..32a8722
--- /dev/null
+++ b/tests/robotests/res/xml/preference_controller_list_helper_fail_invalid_controller.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <Preference
+        android:key="key"
+        settings:controller="com.android.car.settings.common.NonExistentPreferenceController"/>
+</PreferenceScreen>
diff --git a/tests/robotests/res/xml/preference_controller_list_helper_fail_missing_key.xml b/tests/robotests/res/xml/preference_controller_list_helper_fail_missing_key.xml
new file mode 100644
index 0000000..acbb5ae
--- /dev/null
+++ b/tests/robotests/res/xml/preference_controller_list_helper_fail_missing_key.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <Preference
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/tests/robotests/res/xml/preference_controller_list_helper_success.xml b/tests/robotests/res/xml/preference_controller_list_helper_success.xml
new file mode 100644
index 0000000..de81979
--- /dev/null
+++ b/tests/robotests/res/xml/preference_controller_list_helper_success.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <Preference
+        android:key="key1"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <Preference
+        android:key="key2"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <!-- Preferences without controllers should be skipped by the list helper. -->
+    <Preference
+        android:key="key3"/>
+</PreferenceScreen>
diff --git a/tests/robotests/res/xml/preference_parser.xml b/tests/robotests/res/xml/preference_parser.xml
new file mode 100644
index 0000000..8d02f1d
--- /dev/null
+++ b/tests/robotests/res/xml/preference_parser.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 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.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:key="fake_title_key"
+                  android:title="screen_title"
+                  settings:controller="com.android.car.settings.common.FakePreferenceController">
+    <Preference
+        android:key="key1"
+        settings:controller="com.android.car.settings.common.FakePreferenceController"/>
+    <PreferenceCategory
+        android:key="key2"
+        settings:controller="com.android.car.settings.common.FakePreferenceController"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="key3"
+        settings:controller="com.android.car.settings.common.FakePreferenceController"/>
+</PreferenceScreen>
diff --git a/tests/robotests/res/xml/settings_fragment.xml b/tests/robotests/res/xml/settings_fragment.xml
new file mode 100644
index 0000000..0498c50
--- /dev/null
+++ b/tests/robotests/res/xml/settings_fragment.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="screen_title">
+    <Preference
+        android:key="@string/tpk_fake_controller"
+        settings:controller="com.android.car.settings.common.FakePreferenceController"/>
+    <EditTextPreference
+        android:key="@string/tpk_edit_text_preference"/>
+    <ListPreference
+        android:entries="@array/entries"
+        android:entryValues="@array/entry_values"
+        android:key="@string/tpk_list_preference"/>
+</PreferenceScreen>
diff --git a/tests/robotests/res/xml/test_car_volume_items.xml b/tests/robotests/res/xml/test_car_volume_items.xml
new file mode 100644
index 0000000..3ac7903
--- /dev/null
+++ b/tests/robotests/res/xml/test_car_volume_items.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<!--
+  We use a separate test file due to the difficulty accessing android internal resources in tests.
+-->
+<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
+    <item car:usage="voice_communication"
+          car:titleText="@string/test_volume_call"
+          car:icon="@drawable/test_icon"/>
+    <item car:usage="voice_communication_signalling"
+          car:titleText="@string/test_volume_call"
+          car:icon="@drawable/test_icon"/>
+    <item car:usage="media"
+          car:titleText="@string/test_volume_music"
+          car:icon="@drawable/test_icon"/>
+</carVolumeItems>
diff --git a/tests/robotests/res/xml/test_user_details_base_fragment.xml b/tests/robotests/res/xml/test_user_details_base_fragment.xml
new file mode 100644
index 0000000..851425e
--- /dev/null
+++ b/tests/robotests/res/xml/test_user_details_base_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="screen_title">
+    <Preference
+        android:key="@string/tpk_fake_controller"
+        settings:controller="com.android.car.settings.common.FakePreferenceController"/>
+</PreferenceScreen>
diff --git a/tests/robotests/res/xml/two_action_preference_action_shown_not_specified.xml b/tests/robotests/res/xml/two_action_preference_action_shown_not_specified.xml
new file mode 100644
index 0000000..bbf93fc
--- /dev/null
+++ b/tests/robotests/res/xml/two_action_preference_action_shown_not_specified.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <!-- Note that action_shown is not specified -->
+    <com.android.car.settings.common.ButtonPreference
+        android:key="@string/tpk_two_action_preference"
+        android:widgetLayout="@layout/details_preference_widget"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+</PreferenceScreen>
diff --git a/tests/robotests/res/xml/two_action_preference_hidden.xml b/tests/robotests/res/xml/two_action_preference_hidden.xml
new file mode 100644
index 0000000..b703345
--- /dev/null
+++ b/tests/robotests/res/xml/two_action_preference_hidden.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <com.android.car.settings.common.ButtonPreference
+        android:key="@string/tpk_two_action_preference"
+        android:widgetLayout="@layout/details_preference_widget"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"
+        settings:actionShown="false"/>
+</PreferenceScreen>
diff --git a/tests/robotests/res/xml/two_action_preference_shown.xml b/tests/robotests/res/xml/two_action_preference_shown.xml
new file mode 100644
index 0000000..428519b
--- /dev/null
+++ b/tests/robotests/res/xml/two_action_preference_shown.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto">
+    <com.android.car.settings.common.ButtonPreference
+        android:key="@string/tpk_two_action_preference"
+        android:widgetLayout="@layout/details_preference_widget"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"
+        settings:actionShown="true"/>
+</PreferenceScreen>
diff --git a/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java b/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
index bf1f861..ba9b84d 100644
--- a/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
+++ b/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.car.settings;
 
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
 
 import org.junit.runners.model.InitializationError;
 import org.robolectric.RobolectricTestRunner;
@@ -26,14 +27,33 @@
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
- * Custom test runner for the testing of BluetoothPairingDialogs. This is needed because the
- * default behavior for robolectric is just to grab the resource directory in the target package.
+ * Custom test runner for CarSettings. This is needed because the default behavior for
+ * robolectric is just to grab the resource directory in the target package.
  * We want to override this to add several spanning different projects.
  */
 public class CarSettingsRobolectricTestRunner extends RobolectricTestRunner {
+    private static final Map<String, String> AAR_VERSIONS;
+    private static final String SUPPORT_RESOURCE_PATH_TEMPLATE =
+            "jar:file:prebuilts/sdk/current/androidx/m2repository/androidx/"
+                    + "%1$s/%1$s/%2$s/%1$s-%2$s.aar!/res";
+    // contraint-layout aar lives in separate path.
+    // Note its path contains a hyphen.
+    private static final String CONSTRAINT_LAYOUT_RESOURCE_PATH_TEMPLATE =
+            "jar:file:prebuilts/sdk/current/extras/constraint-layout-x/"
+                    + "%1$s/%2$s/%1$s-%2$s.aar!/res";
+
+    static {
+        AAR_VERSIONS = new HashMap<>();
+        AAR_VERSIONS.put("car", "1.0.0-alpha7");
+        AAR_VERSIONS.put("appcompat", "1.1.0-alpha01");
+        AAR_VERSIONS.put("constraintlayout", "1.1.2");
+        AAR_VERSIONS.put("preference", "1.1.0-alpha02");
+    }
 
     /**
      * We don't actually want to change this behavior, so we just call super.
@@ -51,43 +71,65 @@
     }
 
     /**
+     * Create the resource path for a support library component's JAR.
+     */
+    private static String createSupportResourcePathFromJar(@NonNull String componentId) {
+        if (!AAR_VERSIONS.containsKey(componentId)) {
+            throw new IllegalArgumentException("Unknown component " + componentId
+                    + ". Update test with appropriate component name and version.");
+        }
+        if (componentId.equals("constraintlayout")) {
+            return String.format(CONSTRAINT_LAYOUT_RESOURCE_PATH_TEMPLATE, componentId,
+                    AAR_VERSIONS.get(componentId));
+        }
+        return String.format(SUPPORT_RESOURCE_PATH_TEMPLATE, componentId,
+                AAR_VERSIONS.get(componentId));
+    }
+
+    /**
      * We are going to create our own custom manifest so that we can add multiple resource
      * paths to it. This lets us access resources in both Settings and SettingsLib in our tests.
      */
     @Override
     protected AndroidManifest getAppManifest(Config config) {
-        // Using the manifest file's relative path, we can figure out the application directory.
-        final String appRoot = "packages/apps/Car/Settings/";
-        final String manifestPath = appRoot + "/AndroidManifest.xml";
-        final String resDir = appRoot + "/res";
-        final String assetsDir = appRoot + config.assetDir();
+        try {
+            // Using the manifest file's relative path, we can figure out the application directory.
+            URL appRoot = new URL("file:packages/apps/Car/Settings/");
+            URL manifestPath = new URL(appRoot, "AndroidManifest.xml");
+            URL resDir = new URL(appRoot, "tests/robotests/res");
+            URL assetsDir = new URL(appRoot, config.assetDir());
 
-        // By adding any resources from libraries we need to the AndroidManifest, we can access
-        // them from within the parallel universe's resource loader.
-        return new AndroidManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir),
-            Fs.fileFromPath(assetsDir)) {
-            @Override
-            public List<ResourcePath> getIncludedResourcePaths() {
-                List<ResourcePath> paths = super.getIncludedResourcePaths();
-                paths.add(createResourcePath("file:packages/apps/Car/Settings/res"));
+            // By adding any resources from libraries we need to the AndroidManifest, we can access
+            // them from within the parallel universe's resource loader.
+            AndroidManifest androidManifest = new AndroidManifest(Fs.fromURL(manifestPath),
+                    Fs.fromURL(resDir),
+                    Fs.fromURL(assetsDir)) {
+                @Override
+                public List<ResourcePath> getIncludedResourcePaths() {
+                    List<ResourcePath> paths = super.getIncludedResourcePaths();
+                    paths.add(createResourcePath("file:packages/apps/Car/Settings/res"));
 
-                // Support library resources. These need to point to the prebuilts of support
-                // library and not the source.
-                paths.add(createResourcePath(
-                        "file:prebuilts/sdk/current/support/v7/appcompat/res/"));
-                paths.add(createResourcePath("file:prebuilts/sdk/current/support/car/res"));
+                    // Support library resources. These need to point to the prebuilts of support
+                    // library and not the source.
+                    paths.add(createResourcePath(createSupportResourcePathFromJar("appcompat")));
+                    paths.add(createResourcePath(createSupportResourcePathFromJar("car")));
+                    paths.add(createResourcePath(
+                            createSupportResourcePathFromJar("constraintlayout")));
+                    paths.add(createResourcePath(createSupportResourcePathFromJar("preference")));
 
+                    paths.add(
+                            createResourcePath("file:packages/apps/Car/libs/car-settings-lib/res"));
+                    paths.add(createResourcePath("file:frameworks/base/packages/SettingsLib/res"));
 
-                paths.add(createResourcePath("file:packages/apps/Car/libs/car-stream-ui-lib/res "));
-                paths.add(createResourcePath("file:packages/apps/Car/libs/car-list/res"));
-                paths.add(createResourcePath("file:frameworks/base/packages/SettingsLib/res"));
-                paths.add(createResourcePath(
-                        "file:frameworks/opt/setupwizard/library/gingerbread/res"));
-                paths.add(createResourcePath(
-                        "file:frameworks/opt/setupwizard/library/main/res"));
-
-                return paths;
-            }
-        };
+                    return paths;
+                }
+            };
+            // Need to remove this permission since this version of robolectric does not support
+            // multiple protection levels.
+            androidManifest.getPermissions().remove("com.android.car.settings.SET_INITIAL_LOCK");
+            return androidManifest;
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("CarSettingsobolectricTestRunner failure", e);
+        }
     }
 }
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountAutoSyncPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountAutoSyncPreferenceControllerTest.java
new file mode 100644
index 0000000..218faf1
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountAutoSyncPreferenceControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit tests for {@link AccountAutoSyncPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowContentResolver.class, ShadowCarUserManagerHelper.class})
+public class AccountAutoSyncPreferenceControllerTest {
+    private static final int USER_ID = 0;
+    private static final UserHandle USER_HANDLE = new UserHandle(USER_ID);
+
+    private PreferenceControllerTestHelper<AccountAutoSyncPreferenceController> mHelper;
+    private SwitchPreference mSwitchPreference;
+    private AccountAutoSyncPreferenceController mController;
+    private ConfirmationDialogFragment mDialog;
+
+    @Mock
+    private CarUserManagerHelper mMockCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mMockCarUserManagerHelper);
+        when(mMockCarUserManagerHelper.getCurrentProcessUserInfo())
+                .thenReturn(new UserInfo(USER_ID, "name", 0));
+
+        Context context = RuntimeEnvironment.application;
+        mSwitchPreference = new SwitchPreference(application);
+        mHelper = new PreferenceControllerTestHelper<>(application,
+                AccountAutoSyncPreferenceController.class, mSwitchPreference);
+        mController = mHelper.getController();
+        mDialog = new ConfirmationDialogFragment.Builder(context).build();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testOnCreate_hasPreviousDialog_dialogListenerSet() {
+        when(mHelper.getMockFragmentController().findDialogByTag(
+                ConfirmationDialogFragment.TAG)).thenReturn(mDialog);
+        mHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mDialog.getConfirmListener()).isNotNull();
+    }
+
+    @Test
+    public void refreshUi_masterSyncOn_preferenceShouldBeChecked() {
+        mHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        ContentResolver.setMasterSyncAutomaticallyAsUser(true, USER_ID);
+
+        mController.refreshUi();
+
+        assertThat(mSwitchPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_masterSyncOff_preferenceShouldNotBeChecked() {
+        ContentResolver.setMasterSyncAutomaticallyAsUser(false, USER_ID);
+        mHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mSwitchPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceClicked_shouldOpenDialog() {
+        mSwitchPreference.performClick();
+
+        verify(mHelper.getMockFragmentController()).showDialog(
+                any(ConfirmationDialogFragment.class), eq(ConfirmationDialogFragment.TAG));
+    }
+
+    @Test
+    public void onConfirm_shouldTogglePreference() {
+        // Set the preference as unchecked first so that the state is known
+        mHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        ContentResolver.setMasterSyncAutomaticallyAsUser(false, USER_ID);
+        mController.refreshUi();
+
+        assertThat(mSwitchPreference.isChecked()).isFalse();
+
+        Bundle arguments = new Bundle();
+        arguments.putBoolean(AccountAutoSyncPreferenceController.KEY_ENABLING, true);
+        arguments.putParcelable(AccountAutoSyncPreferenceController.KEY_USER_HANDLE, USER_HANDLE);
+        mController.mConfirmListener.onConfirm(arguments);
+
+        assertThat(mSwitchPreference.isChecked()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsFragmentTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsFragmentTest.java
new file mode 100644
index 0000000..2a9c08f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsFragmentTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+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;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/**
+ * Tests for the {@link AccountDetailsFragment}.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowAccountManager.class,
+        ShadowContentResolver.class})
+public class AccountDetailsFragmentTest {
+    private static final String DIALOG_TAG = "confirmRemoveAccount";
+    private final Account mAccount = new Account("Name", "com.acct");
+    private final UserInfo mUserInfo = new UserInfo(/* id= */ 0, /* name= */ "name", /* flags= */
+            0);
+    private final CharSequence mAccountLabel = "Type 1";
+
+    private Context mContext;
+    private BaseTestActivity mActivity;
+    private AccountDetailsFragment mFragment;
+    @Mock
+    private CarUserManagerHelper mMockCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mMockCarUserManagerHelper);
+
+        mContext = application;
+        // Add the account to the official list of accounts
+        getShadowAccountManager().addAccount(mAccount);
+
+        mActivity = Robolectric.setupActivity(BaseTestActivity.class);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowContentResolver.reset();
+        mActivity.clearOnBackPressedFlag();
+    }
+
+    @Test
+    public void onActivityCreated_titleShouldBeSet() {
+        initFragment();
+
+        TextView title = mFragment.requireActivity().findViewById(R.id.title);
+        assertThat(title.getText()).isEqualTo(mAccountLabel);
+    }
+
+    @Test
+    public void cannotModifyUsers_removeAccountButtonShouldNotBeVisible() {
+        doReturn(false).when(mMockCarUserManagerHelper).canCurrentProcessModifyAccounts();
+        initFragment();
+
+        Button removeAccountButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        assertThat(removeAccountButton.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void canModifyUsers_removeAccountButtonShouldBeVisible() {
+        doReturn(true).when(mMockCarUserManagerHelper).canCurrentProcessModifyAccounts();
+        initFragment();
+
+        Button removeAccountButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        assertThat(removeAccountButton.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onRemoveAccountButtonClicked_canModifyUsers_shouldShowConfirmRemoveAccountDialog() {
+        doReturn(true).when(mMockCarUserManagerHelper).canCurrentProcessModifyAccounts();
+        initFragment();
+
+        Button removeAccountButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        removeAccountButton.performClick();
+
+        Fragment dialogFragment =
+                mFragment.requireActivity().getSupportFragmentManager().findFragmentByTag(
+                        DIALOG_TAG);
+
+        assertThat(dialogFragment).isNotNull();
+        assertThat(dialogFragment).isInstanceOf(
+                AccountDetailsFragment.ConfirmRemoveAccountDialogFragment.class);
+    }
+
+    @Test
+    public void accountExists_accountStillExists_shouldBeTrue() {
+        initFragment();
+
+        // Nothing has happened to the account so this should return true;
+        assertThat(mFragment.accountExists()).isTrue();
+    }
+
+    @Test
+    public void accountExists_accountWasRemoved_shouldBeFalse() {
+        initFragment();
+
+        // Clear accounts so that the account being displayed appears to have been removed
+        getShadowAccountManager().removeAllAccounts();
+        assertThat(mFragment.accountExists()).isFalse();
+    }
+
+    @Test
+    public void onAccountsUpdate_accountDoesNotExist_shouldGoBack() {
+        initFragment();
+
+        // Clear accounts so that the account being displayed appears to have been removed
+        getShadowAccountManager().removeAllAccounts();
+        mFragment.onAccountsUpdate(null);
+
+        assertThat(mActivity.getOnBackPressedFlag()).isTrue();
+    }
+
+    private void initFragment() {
+        mFragment = AccountDetailsFragment.newInstance(mAccount, mAccountLabel, mUserInfo);
+        mActivity.launchFragment(mFragment);
+    }
+
+    private ShadowAccountManager getShadowAccountManager() {
+        return Shadow.extract(AccountManager.get(mContext));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsPreferenceControllerTest.java
new file mode 100644
index 0000000..0b375f4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsPreferenceControllerTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.RuntimeEnvironment.application;
+import static org.testng.Assert.assertThrows;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.os.UserHandle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link AccountDetailsPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class,
+        ShadowApplicationPackageManager.class})
+public class AccountDetailsPreferenceControllerTest {
+    private static final String ACCOUNT_NAME = "Name";
+    private static final String ACCOUNT_TYPE = "com.acct";
+    private final Account mAccount = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+    private final UserHandle mUserHandle = new UserHandle(0);
+
+    private AccountDetailsPreferenceController mController;
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        PreferenceControllerTestHelper<AccountDetailsPreferenceController> helper =
+                new PreferenceControllerTestHelper<>(application,
+                        AccountDetailsPreferenceController.class);
+        mController = helper.getController();
+        mController.setAccount(mAccount);
+        mController.setUserHandle(mUserHandle);
+
+        mPreference = new Preference(application);
+        helper.setPreference(mPreference);
+        helper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void checkInitialized_accountSetAndUserHandleSet_doesNothing() {
+        mController = new PreferenceControllerTestHelper<>(application,
+                AccountDetailsPreferenceController.class).getController();
+        mController.setAccount(mAccount);
+        mController.setUserHandle(mUserHandle);
+
+        mController.checkInitialized();
+    }
+
+    @Test
+    public void checkInitialized_nullAccount_throwsIllegalStateException() {
+        mController = new PreferenceControllerTestHelper<>(application,
+                AccountDetailsPreferenceController.class).getController();
+        mController.setUserHandle(mUserHandle);
+
+        assertThrows(IllegalStateException.class, () -> mController.checkInitialized());
+    }
+
+    @Test
+    public void checkInitialized_nullUserHandle_throwsIllegalStateException() {
+        mController = new PreferenceControllerTestHelper<>(application,
+                AccountDetailsPreferenceController.class).getController();
+        mController.setAccount(mAccount);
+
+        assertThrows(IllegalStateException.class, () -> mController.checkInitialized());
+    }
+
+    @Test
+    public void refreshUi_shouldSetTitle() {
+        mController.refreshUi();
+
+        assertThat(mPreference.getTitle().toString()).isEqualTo(ACCOUNT_NAME);
+    }
+
+    @Test
+    public void refreshUi_shouldSetIcon() {
+        // Add authenticator description with icon resource
+        addAuthenticator(/* type= */ ACCOUNT_TYPE, /* labelRes= */
+                R.string.account_type1_label, /* iconId= */ R.drawable.ic_add);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getIcon()).isNotNull();
+        assertThat(Shadows.shadowOf(mPreference.getIcon()).getCreatedFromResId()).isEqualTo(
+                R.drawable.ic_add);
+    }
+
+    private void addAuthenticator(String type, int labelRes, int iconId) {
+        getShadowAccountManager().addAuthenticator(
+                new AuthenticatorDescription(type, "com.android.car.settings",
+                        labelRes, iconId, /* smallIconId= */ 0, /* prefId= */ 0,
+                        /* customTokens= */ false));
+    }
+
+    private ShadowAccountManager getShadowAccountManager() {
+        return Shadow.extract(AccountManager.get(application));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsSettingControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsSettingControllerTest.java
new file mode 100644
index 0000000..326f56c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsSettingControllerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.robolectric.RuntimeEnvironment.application;
+import static org.testng.Assert.assertThrows;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ExtraSettingsLoader;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Unit test for {@link AccountDetailsSettingController}.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class,
+        ShadowApplicationPackageManager.class})
+public class AccountDetailsSettingControllerTest {
+
+    private static final String ACCOUNT_NAME = "account_name";
+    private static final String MATCHING_ACCOUNT_TYPE = "account_type";
+    private static final String ACCOUNT_ACCESS_ID = "account_access_id";
+    private static final String METADATA_IA_ACCOUNT = "com.android.settings.ia.account";
+    private static final String NOT_MATCHING_ACCOUNT_TYPE = "com.android.settings.ia.account";
+
+    private Context mContext;
+    private PreferenceGroup mPreference;
+    @Mock
+    private ExtraSettingsLoader mExtraSettingsLoader;
+    private AccountDetailsSettingController mAccountDetailsSettingController;
+    private Account mAccount = new Account(ACCOUNT_NAME, MATCHING_ACCOUNT_TYPE, ACCOUNT_ACCESS_ID);
+    private List<Preference> mPreferenceList;
+    private HashMap<Preference, Bundle> mPreferenceBundleMap;
+    PreferenceControllerTestHelper<AccountDetailsSettingController> mHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+
+        mHelper = new PreferenceControllerTestHelper<>(application,
+                AccountDetailsSettingController.class);
+
+        mAccountDetailsSettingController = mHelper.getController();
+        mAccountDetailsSettingController.setAccount(mAccount);
+
+        mPreferenceList = new ArrayList<>();
+        mPreferenceBundleMap = new HashMap<>();
+        mPreference = new LogicalPreferenceGroup(application);
+        mPreference.setIntent(new Intent());
+        mHelper.setPreference(mPreference);
+    }
+
+    @Test
+    public void checkInitialized_accountSetAndUserHandleSet_doesNothing() {
+        mAccountDetailsSettingController = new PreferenceControllerTestHelper<>(application,
+                AccountDetailsSettingController.class).getController();
+        mAccountDetailsSettingController.setAccount(mAccount);
+
+        mAccountDetailsSettingController.checkInitialized();
+    }
+
+    @Test
+    public void checkInitialized_nullAccount_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class,
+                () -> new PreferenceControllerTestHelper<>(mContext,
+                        AccountDetailsSettingController.class,
+                        new LogicalPreferenceGroup(mContext)));
+    }
+
+    @Test
+    public void addExtraSettings_preferenceEmpty_shouldNotAddAnyPreferences() {
+        mHelper.markState(Lifecycle.State.STARTED);
+
+        setupMockSettingLoaderAndRefreshUI();
+
+        assertThat(mPreference.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void addExtraSettings_preferenceEmpty_isNotVisible() {
+        mHelper.markState(Lifecycle.State.STARTED);
+
+        setupMockSettingLoaderAndRefreshUI();
+
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void addExtraSettings_accountTypeNotEqual_shouldNotAddAnyPreferences() {
+        mHelper.markState(Lifecycle.State.STARTED);
+        Preference preference = new Preference(mContext);
+        mPreferenceList.add(preference);
+        Bundle bundle = new Bundle();
+        bundle.putString(METADATA_IA_ACCOUNT, NOT_MATCHING_ACCOUNT_TYPE);
+        mPreferenceBundleMap.put(preference, bundle);
+
+        setupMockSettingLoaderAndRefreshUI();
+
+        assertThat(mPreference.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void addExtraSettings_accountTypeNotEqual_isNotVisible() {
+        mHelper.markState(Lifecycle.State.STARTED);
+        Preference preference = new Preference(mContext);
+        mPreferenceList.add(preference);
+        Bundle bundle = new Bundle();
+        bundle.putString(METADATA_IA_ACCOUNT, NOT_MATCHING_ACCOUNT_TYPE);
+        mPreferenceBundleMap.put(preference, bundle);
+
+        setupMockSettingLoaderAndRefreshUI();
+
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void addExtraSettings_accountTypeEqual_shouldAddPreferences() {
+        Preference preference = new Preference(mContext);
+        mPreferenceList.add(preference);
+        Bundle bundle = new Bundle();
+        bundle.putString(METADATA_IA_ACCOUNT, MATCHING_ACCOUNT_TYPE);
+        mPreferenceBundleMap.put(preference, bundle);
+
+        setupMockSettingLoaderAndRefreshUI();
+        mHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mPreference.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void addExtraSettings_accountTypeEqual_isVisible() {
+        Preference preference = new Preference(mContext);
+        mPreferenceList.add(preference);
+        Bundle bundle = new Bundle();
+        bundle.putString(METADATA_IA_ACCOUNT, MATCHING_ACCOUNT_TYPE);
+        mPreferenceBundleMap.put(preference, bundle);
+
+        setupMockSettingLoaderAndRefreshUI();
+        mHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mPreference.isVisible()).isTrue();
+    }
+
+    private void setupMockSettingLoaderAndRefreshUI() {
+        when(mExtraSettingsLoader.loadPreferences(any())).thenReturn(mPreferenceBundleMap);
+
+        mAccountDetailsSettingController.setExtraSettingsLoader(mExtraSettingsLoader);
+        mAccountDetailsSettingController.refreshUi();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsWithSyncStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsWithSyncStatusPreferenceControllerTest.java
new file mode 100644
index 0000000..f70d499
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountDetailsWithSyncStatusPreferenceControllerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncAdapterType;
+import android.content.SyncStatusInfo;
+import android.content.SyncStatusObserver;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.ProviderInfo;
+import android.os.UserHandle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link AccountDetailsWithSyncStatusPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class,
+        ShadowApplicationPackageManager.class})
+public class AccountDetailsWithSyncStatusPreferenceControllerTest {
+    private static final int SYNCABLE = 1;
+    private static final String AUTHORITY = "authority";
+    private static final String ACCOUNT_NAME = "Name";
+    private static final String ACCOUNT_TYPE = "com.acct";
+    private final Account mAccount = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+    private final UserHandle mUserHandle = new UserHandle(0);
+
+    private Context mContext;
+    private AccountDetailsWithSyncStatusPreferenceController mController;
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        mContext = application;
+        PreferenceControllerTestHelper<AccountDetailsWithSyncStatusPreferenceController> helper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        AccountDetailsWithSyncStatusPreferenceController.class);
+        mController = helper.getController();
+        mController.setAccount(mAccount);
+        mController.setUserHandle(mUserHandle);
+
+        mPreference = new Preference(application);
+        helper.setPreference(mPreference);
+        helper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void refreshUi_syncIsNotFailing_summaryShouldBeBlank() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo("");
+    }
+
+    @Test
+    public void refreshUi_syncIsFailing_summaryShouldBeSet() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        // Turns on automatic sync for the the sync adapter.
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ true,
+                mUserHandle.getIdentifier());
+        // Sets the sync adapter's last failure time and message so it appears to have failed
+        // previously.
+        SyncStatusInfo status = new SyncStatusInfo(0);
+        status.lastFailureTime = 10;
+        status.lastFailureMesg = "too-many-deletions";
+        ShadowContentResolver.setSyncStatus(mAccount, AUTHORITY, status);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.sync_is_failing));
+    }
+
+    @Test
+    public void onSyncStatusChanged_summaryShouldUpdated() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        // Make sure the summary is blank first
+        mController.refreshUi();
+        assertThat(mPreference.getSummary()).isEqualTo("");
+
+        // Turns on automatic sync for the the sync adapter.
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ true,
+                mUserHandle.getIdentifier());
+        // Sets the sync adapter's last failure time and message so it appears to have failed
+        // previously.
+        SyncStatusInfo status = new SyncStatusInfo(0);
+        status.lastFailureTime = 10;
+        status.lastFailureMesg = "too-many-deletions";
+        ShadowContentResolver.setSyncStatus(mAccount, AUTHORITY, status);
+
+        SyncStatusObserver listener = ShadowContentResolver.getStatusChangeListener();
+        listener.onStatusChanged(/* which= */ 0);
+
+        assertThat(mPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.sync_is_failing));
+    }
+
+    private void setUpVisibleSyncAdapters(String... authorities) {
+        SyncAdapterType[] syncAdapters = new SyncAdapterType[authorities.length];
+        for (int i = 0; i < authorities.length; i++) {
+            String authority = authorities[i];
+            // Adds a sync adapter type that has the right account type and is visible.
+            SyncAdapterType syncAdapterType = new SyncAdapterType(authority,
+                    ACCOUNT_TYPE, /* userVisible */ true, /* supportsUploading */ true);
+            syncAdapters[i] = syncAdapterType;
+
+            // Sets that the sync adapter is syncable.
+            ShadowContentResolver.setIsSyncable(mAccount, authority, /* syncable= */ SYNCABLE);
+
+            // Sets provider info with a label for the sync adapter.
+            ProviderInfo info = new ProviderInfo();
+            info.authority = authority;
+            info.name = authority;
+            // Set an application info to avoid an NPE
+            info.applicationInfo = new ApplicationInfo();
+            ProviderInfo[] providers = {info};
+
+            PackageInfo packageInfo = new PackageInfo();
+            packageInfo.packageName = authority;
+            packageInfo.providers = providers;
+            getShadowApplicationManager().addPackage(packageInfo);
+        }
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+    }
+
+    private ShadowApplicationPackageManager getShadowApplicationManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountListPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountListPreferenceControllerTest.java
new file mode 100644
index 0000000..9da11e4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountListPreferenceControllerTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+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;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit tests for {@link AccountListPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowContentResolver.class,
+        ShadowAccountManager.class})
+public class AccountListPreferenceControllerTest {
+    private static final int USER_ID = 0;
+    private static final String USER_NAME = "name";
+    private static final int NOT_THIS_USER_ID = 1;
+    private PreferenceControllerTestHelper<AccountListPreferenceController> mHelper;
+    private AccountManager mAccountManager = AccountManager.get(application);
+    private PreferenceCategory mPreferenceCategory;
+    private AccountListPreferenceController mController;
+    private FragmentController mFragmentController;
+
+    @Mock
+    private CarUserManagerHelper mMockCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Set up user info
+        ShadowCarUserManagerHelper.setMockInstance(mMockCarUserManagerHelper);
+        doReturn(new UserInfo(USER_ID, USER_NAME, 0)).when(
+                mMockCarUserManagerHelper).getCurrentProcessUserInfo();
+        doReturn(true).when(
+                mMockCarUserManagerHelper).canCurrentProcessModifyAccounts();
+
+        // Add authenticated account types so that they are listed below
+        addAuthenticator(/* type= */ "com.acct1", /* labelRes= */ R.string.account_type1_label);
+        addAuthenticator(/* type= */ "com.acct2", /* labelRes= */ R.string.account_type2_label);
+
+        mPreferenceCategory = new PreferenceCategory(application);
+        mHelper = new PreferenceControllerTestHelper<>(application,
+                AccountListPreferenceController.class, mPreferenceCategory);
+        mHelper.markState(Lifecycle.State.CREATED);
+        mController = mHelper.getController();
+        mFragmentController = mHelper.getMockFragmentController();
+    }
+
+    @After
+    public void reset() {
+        removeAllAccounts();
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void onCreate_preferenceCategoryTitleShouldBeSet() {
+        String expectedTitle = application.getString(R.string.account_list_title, "name");
+        assertThat(mPreferenceCategory.getTitle()).isEqualTo(expectedTitle);
+    }
+
+    @Test
+    public void refreshUi_hasNoAccounts_shouldDisplayNoAccountPref() {
+        mController.refreshUi();
+
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1);
+        Preference noAccountPref = mPreferenceCategory.getPreference(0);
+
+        assertThat(noAccountPref.getTitle()).isEqualTo(
+                application.getString(R.string.no_accounts_added));
+    }
+
+    @Test
+    public void refreshUi_hasAccounts_shouldDisplayAccounts() {
+        addAccount(/* name= */ "Account1", /* type= */ "com.acct1");
+        addAccount(/* name= */ "Account2", /* type= */ "com.acct2");
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
+
+        Preference firstPref = mPreferenceCategory.getPreference(0);
+        assertThat(firstPref.getTitle()).isEqualTo("Account1");
+        assertThat(firstPref.getSummary()).isEqualTo("Type 1");
+
+        Preference secondPref = mPreferenceCategory.getPreference(1);
+        assertThat(secondPref.getTitle()).isEqualTo("Account2");
+        assertThat(secondPref.getSummary()).isEqualTo("Type 2");
+    }
+
+    @Test
+    public void refreshUi_hasUnauthenticatedAccount_shouldNotDisplayAccount() {
+        addAccount(/* name= */ "Account1", /* type= */ "com.acct1");
+        addAccount(/* name= */ "Account2", /* type= */ "com.acct2");
+        // There is not authenticator for account type "com.acct3" so this account should not
+        // appear in the list of displayed accounts.
+        addAccount(/* name= */ "Account3", /* type= */ "com.acct3");
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
+
+        Preference firstPref = mPreferenceCategory.getPreference(0);
+        assertThat(firstPref.getTitle()).isEqualTo("Account1");
+        assertThat(firstPref.getSummary()).isEqualTo("Type 1");
+
+        Preference secondPref = mPreferenceCategory.getPreference(1);
+        assertThat(secondPref.getTitle()).isEqualTo("Account2");
+        assertThat(secondPref.getSummary()).isEqualTo("Type 2");
+    }
+
+    @Test
+    public void onAccountsUpdate_isThisUser_shouldForceUpdate() {
+        addAccount(/* name= */ "Account1", /* type= */ "com.acct1");
+        addAccount(/* name= */ "Account2", /* type= */ "com.acct2");
+
+        mController.refreshUi();
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
+
+        getShadowAccountManager().removeAllAccounts();
+        addAccount(/* name= */ "Account3", /* type= */ "com.acct1");
+
+        mController.onAccountsUpdate(new UserHandle(USER_ID));
+
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1);
+        Preference firstPref = mPreferenceCategory.getPreference(0);
+        assertThat(firstPref.getTitle()).isEqualTo("Account3");
+        assertThat(firstPref.getSummary()).isEqualTo("Type 1");
+    }
+
+    @Test
+    public void onAccountsUpdate_updatedUserIsNotCurrentUser_shouldNotForceUpdate() {
+        addAccount(/* name= */ "Account1", /* type= */ "com.acct1");
+        addAccount(/* name= */ "Account2", /* type= */ "com.acct2");
+
+        mController.refreshUi();
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
+
+        getShadowAccountManager().removeAllAccounts();
+        addAccount(/* name= */ "Account3", /* type= */ "com.acct1");
+
+        mController.onAccountsUpdate(new UserHandle(NOT_THIS_USER_ID));
+
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void onUsersUpdate_shouldForceUpdate() {
+        addAccount(/* name= */ "Account1", /* type= */ "com.acct1");
+        addAccount(/* name= */ "Account2", /* type= */ "com.acct2");
+
+        mController.refreshUi();
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
+
+        getShadowAccountManager().removeAllAccounts();
+        addAccount(/* name= */ "Account3", /* type= */ "com.acct1");
+
+        mController.onUsersUpdate();
+
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1);
+        Preference firstPref = mPreferenceCategory.getPreference(0);
+        assertThat(firstPref.getTitle()).isEqualTo("Account3");
+        assertThat(firstPref.getSummary()).isEqualTo("Type 1");
+    }
+
+    @Test
+    public void onAccountPreferenceClicked_shouldLaunchAccountDetailsFragment() {
+        addAccount(/* name= */ "Account1", /* type= */ "com.acct1");
+        mController.refreshUi();
+
+        Preference firstPref = mPreferenceCategory.getPreference(0);
+        firstPref.performClick();
+
+        verify(mFragmentController).launchFragment(any(AccountDetailsFragment.class));
+    }
+
+    private void addAccount(String name, String type) {
+        getShadowAccountManager().addAccount(new Account(name, type));
+    }
+
+    private void removeAllAccounts() {
+        getShadowAccountManager().removeAllAccounts();
+    }
+
+    private void addAuthenticator(String type, int labelRes) {
+        getShadowAccountManager().addAuthenticator(
+                new AuthenticatorDescription(type, "com.android.car.settings",
+                        labelRes, 0, 0, 0, false));
+    }
+
+    private ShadowAccountManager getShadowAccountManager() {
+        return Shadow.extract(mAccountManager);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountSettingsFragmentTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountSettingsFragmentTest.java
new file mode 100644
index 0000000..bf3da2e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountSettingsFragmentTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.pm.UserInfo;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+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;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests for AccountSettingsFragment class.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowAccountManager.class,
+        ShadowContentResolver.class})
+public class AccountSettingsFragmentTest {
+    private BaseTestActivity mActivity;
+    private AccountSettingsFragment mFragment;
+
+    @Mock
+    private CarUserManagerHelper mMockCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Set up user info
+        ShadowCarUserManagerHelper.setMockInstance(mMockCarUserManagerHelper);
+        doReturn(new UserInfo()).when(
+                mMockCarUserManagerHelper).getCurrentProcessUserInfo();
+
+        mActivity = Robolectric.setupActivity(BaseTestActivity.class);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void cannotModifyUsers_addAccountButtonShouldNotBeVisible() {
+        doReturn(false).when(mMockCarUserManagerHelper).canCurrentProcessModifyAccounts();
+        initFragment();
+
+        Button addAccountButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        assertThat(addAccountButton.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void canModifyUsers_addAccountButtonShouldBeVisible() {
+        doReturn(true).when(mMockCarUserManagerHelper).canCurrentProcessModifyAccounts();
+        initFragment();
+
+        Button addAccountButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        assertThat(addAccountButton.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void clickAddAccountButton_shouldOpenChooseAccountFragment() {
+        doReturn(true).when(mMockCarUserManagerHelper).canCurrentProcessModifyAccounts();
+        initFragment();
+
+        Button addAccountButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        addAccountButton.performClick();
+
+        assertThat(mFragment.getFragmentManager().findFragmentById(
+                R.id.fragment_container)).isInstanceOf(ChooseAccountFragment.class);
+    }
+
+    private void initFragment() {
+        mFragment = new AccountSettingsFragment();
+        mActivity.launchFragment(mFragment);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountSyncDetailsFragmentTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountSyncDetailsFragmentTest.java
new file mode 100644
index 0000000..37ce2a4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountSyncDetailsFragmentTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.SyncAdapterType;
+import android.content.SyncInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.widget.Button;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+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;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the {@link AccountSyncDetailsFragment}.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowContentResolver.class, ShadowAccountManager.class})
+public class AccountSyncDetailsFragmentTest {
+    private static final int USER_ID = 3;
+    private static final String ACCOUNT_TYPE = "com.acct1";
+    private static final String AUTHORITY = "authority";
+    private static final String AUTHORITY_2 = "authority2";
+
+    private final Account mAccount = new Account("Name", ACCOUNT_TYPE);
+    private final UserHandle mUserHandle = new UserHandle(USER_ID);
+
+    private BaseTestActivity mActivity;
+    private AccountSyncDetailsFragment mFragment;
+    @Mock
+    ShadowContentResolver.SyncListener mMockSyncListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mActivity = Robolectric.setupActivity(BaseTestActivity.class);
+        ShadowContentResolver.setSyncListener(mMockSyncListener);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void onInit_doesNotHaveActiveSyncs_actionButtonShouldSaySyncNow() {
+        initFragment();
+
+        Button syncButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        assertThat(syncButton.getText()).isEqualTo(
+                application.getString(R.string.sync_button_sync_now));
+    }
+
+    @Test
+    public void onInit_hasActiveSyncs_actionButtonShouldSayCancelSync() {
+        SyncInfo syncInfo = mock(SyncInfo.class);
+        List<SyncInfo> syncs = new ArrayList<>();
+        syncs.add(syncInfo);
+        ShadowContentResolver.setCurrentSyncs(syncs);
+
+        initFragment();
+
+        Button syncButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        assertThat(syncButton.getText()).isEqualTo(
+                application.getString(R.string.sync_button_sync_cancel));
+    }
+
+    @Test
+    public void onButtonClicked_doesNotHaveActiveSyncs_shouldSyncSyncableAdapters() {
+        setUpSyncAdapters(AUTHORITY, AUTHORITY_2);
+        initFragment();
+
+        Button syncButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        syncButton.performClick();
+
+        ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
+        verify(mMockSyncListener, times(2)).onSyncRequested(eq(mAccount), argument.capture(),
+                eq(USER_ID), any(Bundle.class));
+
+        List<String> values = argument.getAllValues();
+
+        assertThat(values).contains(AUTHORITY);
+        assertThat(values).contains(AUTHORITY_2);
+    }
+
+    @Test
+    public void onButtonClicked_doesNotHaveActiveSyncs_oneTimeSyncIsOff_shouldNotSyncOffAdapters() {
+        setUpSyncAdapters(AUTHORITY, AUTHORITY_2);
+        // Turns off one time sync and automatic sync for the adapter
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ true, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY_2, /* sync= */ false,
+                USER_ID);
+        initFragment();
+
+        Button syncButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        syncButton.performClick();
+
+        ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
+        verify(mMockSyncListener, times(1)).onSyncRequested(eq(mAccount), argument.capture(),
+                eq(USER_ID), any(Bundle.class));
+
+        List<String> values = argument.getAllValues();
+
+        assertThat(values).contains(AUTHORITY);
+        assertThat(values).doesNotContain(AUTHORITY_2);
+    }
+
+    @Test
+    public void onButtonClicked_doesNotHaveActiveSyncs_oneTimeSyncIsOn_shouldSyncOffAdapters() {
+        setUpSyncAdapters(AUTHORITY, AUTHORITY_2);
+        // Turns on one time sync and turns off automatic sync for the adapter
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ false, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY_2, /* sync= */ false,
+                USER_ID);
+        initFragment();
+
+        Button syncButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        syncButton.performClick();
+
+        ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
+        verify(mMockSyncListener, times(2)).onSyncRequested(eq(mAccount), argument.capture(),
+                eq(USER_ID), any(Bundle.class));
+
+        List<String> values = argument.getAllValues();
+
+        assertThat(values).contains(AUTHORITY);
+        assertThat(values).contains(AUTHORITY_2);
+    }
+
+    @Test
+    public void onButtonClicked_doesHaveActiveSyncs_shouldCancelSyncForSyncableAdapters() {
+        // Add active syncs
+        SyncInfo syncInfo = mock(SyncInfo.class);
+        List<SyncInfo> syncs = new ArrayList<>();
+        syncs.add(syncInfo);
+        ShadowContentResolver.setCurrentSyncs(syncs);
+
+        setUpSyncAdapters(AUTHORITY, AUTHORITY_2);
+        initFragment();
+
+        Button syncButton = mFragment.requireActivity().findViewById(R.id.action_button1);
+        syncButton.performClick();
+
+        ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
+        verify(mMockSyncListener, times(2)).onSyncCanceled(eq(mAccount), argument.capture(),
+                eq(USER_ID));
+
+        List<String> values = argument.getAllValues();
+
+        assertThat(values).contains(AUTHORITY);
+        assertThat(values).contains(AUTHORITY_2);
+    }
+
+    private void initFragment() {
+        mFragment = AccountSyncDetailsFragment.newInstance(mAccount, mUserHandle);
+        mActivity.launchFragment(mFragment);
+    }
+
+    private void setUpSyncAdapters(String... authorities) {
+        SyncAdapterType[] syncAdapters = new SyncAdapterType[authorities.length];
+        for (int i = 0; i < authorities.length; i++) {
+            String authority = authorities[i];
+            // Adds a sync adapter type that has the right account type and is visible.
+            SyncAdapterType syncAdapterType = new SyncAdapterType(authority,
+                    ACCOUNT_TYPE, /* userVisible */ true, /* supportsUploading */ true);
+            syncAdapters[i] = syncAdapterType;
+
+            // Sets that the sync adapter is syncable.
+            ShadowContentResolver.setIsSyncable(mAccount, authority, /* syncable= */ 1);
+        }
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountSyncDetailsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountSyncDetailsPreferenceControllerTest.java
new file mode 100644
index 0000000..f7407ff
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountSyncDetailsPreferenceControllerTest.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncAdapterType;
+import android.content.SyncInfo;
+import android.content.SyncStatusInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.ProviderInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+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;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Unit test for {@link AccountSyncDetailsPreferenceController}.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowContentResolver.class, ShadowApplicationPackageManager.class,
+        ShadowAccountManager.class})
+public class AccountSyncDetailsPreferenceControllerTest {
+    private static final int SYNCABLE = 1;
+    private static final int NOT_SYNCABLE = 0;
+
+    private static final int USER_ID = 3;
+    private static final int NOT_USER_ID = 5;
+
+    private static final String AUTHORITY = "authority";
+    private static final String ACCOUNT_TYPE = "com.acct1";
+    private static final String DIFFERENT_ACCOUNT_TYPE = "com.acct2";
+
+    private final Account mAccount = new Account("acct1", ACCOUNT_TYPE);
+    private final UserHandle mUserHandle = new UserHandle(USER_ID);
+    @Mock
+    ShadowContentResolver.SyncListener mMockSyncListener;
+    private Context mContext;
+    private AccountSyncDetailsPreferenceController mController;
+    private LogicalPreferenceGroup mPreferenceGroup;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = application;
+        ShadowContentResolver.setSyncListener(mMockSyncListener);
+
+        PreferenceControllerTestHelper<AccountSyncDetailsPreferenceController> helper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        AccountSyncDetailsPreferenceController.class);
+        mController = helper.getController();
+        mController.setAccount(mAccount);
+        mController.setUserHandle(mUserHandle);
+
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        helper.setPreference(mPreferenceGroup);
+
+        helper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void refreshUi_syncAdapterDoesNotHaveSameAccountType_shouldNotBeShown() {
+        // Adds a sync adapter type that is visible but does not have the right account type.
+        SyncAdapterType syncAdapterType = new SyncAdapterType(AUTHORITY,
+                DIFFERENT_ACCOUNT_TYPE, /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_syncAdapterIsNotVisible_shouldNotBeShown() {
+        // Adds a sync adapter type that has the right account type but is not visible.
+        SyncAdapterType syncAdapterType = new SyncAdapterType(AUTHORITY,
+                ACCOUNT_TYPE, /* userVisible */ false, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_syncAdapterIsNotSyncable_shouldNotBeShown() {
+        // Adds a sync adapter type that has the right account type and is visible.
+        SyncAdapterType syncAdapterType = new SyncAdapterType(AUTHORITY,
+                ACCOUNT_TYPE, /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        // Sets that the sync adapter to not syncable.
+        ShadowContentResolver.setIsSyncable(mAccount, AUTHORITY, /* syncable= */ NOT_SYNCABLE);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_syncAdapterDoesNotHaveProviderInfo_shouldNotBeShown() {
+        // Adds a sync adapter type that has the right account type and is visible.
+        SyncAdapterType syncAdapterType = new SyncAdapterType(AUTHORITY,
+                ACCOUNT_TYPE, /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        // Sets that the sync adapter to syncable.
+        ShadowContentResolver.setIsSyncable(mAccount, AUTHORITY, /* syncable= */ SYNCABLE);
+
+        // However, no provider info is set for the sync adapter, so it shouldn't be visible.
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_providerInfoDoesNotHaveLabel_shouldNotBeShown() {
+        // Adds a sync adapter type that has the right account type and is visible.
+        SyncAdapterType syncAdapterType = new SyncAdapterType(AUTHORITY,
+                ACCOUNT_TYPE, /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        // Sets that the sync adapter to syncable.
+        ShadowContentResolver.setIsSyncable(mAccount, AUTHORITY, /* syncable= */ SYNCABLE);
+        // Sets provider info for the sync adapter but it does not have a label.
+        ProviderInfo info = new ProviderInfo();
+        info.authority = AUTHORITY;
+        info.name = "";
+        // Set an application info to avoid an NPE
+        info.applicationInfo = new ApplicationInfo();
+
+        ProviderInfo[] providers = {info};
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = AUTHORITY;
+        packageInfo.providers = providers;
+        getShadowApplicationManager().addPackage(packageInfo);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_providerLabelShouldBeSet() {
+        // Adds a sync adapter type that has the right account type and is visible.
+        SyncAdapterType syncAdapterType = new SyncAdapterType(AUTHORITY,
+                ACCOUNT_TYPE, /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        // Sets that the sync adapter to syncable.
+        ShadowContentResolver.setIsSyncable(mAccount, AUTHORITY, /* syncable= */ SYNCABLE);
+        // Sets provider info for the sync adapter with a label.
+        ProviderInfo info = new ProviderInfo();
+        info.authority = AUTHORITY;
+        info.name = "label";
+        // Set an application info to avoid an NPE
+        info.applicationInfo = new ApplicationInfo();
+
+        ProviderInfo[] providers = {info};
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = AUTHORITY;
+        packageInfo.providers = providers;
+        getShadowApplicationManager().addPackage(packageInfo);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        Preference pref = mPreferenceGroup.getPreference(0);
+        assertThat(pref.getTitle()).isEqualTo("label");
+    }
+
+    @Test
+    public void refreshUi_masterSyncOff_syncDisabled_shouldNotBeChecked() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Turns off master sync and automatic sync for the adapter.
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ true, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ false,
+                USER_ID);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        SyncPreference pref = (SyncPreference) mPreferenceGroup.getPreference(0);
+        assertThat(pref.isChecked()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_masterSyncOn_syncDisabled_shouldBeChecked() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Turns on master sync and turns off automatic sync for the adapter.
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ false, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ false,
+                USER_ID);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        SyncPreference pref = (SyncPreference) mPreferenceGroup.getPreference(0);
+        assertThat(pref.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_masterSyncOff_syncEnabled_shouldBeChecked() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Turns off master sync and turns on automatic sync for the adapter.
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ true, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ true,
+                USER_ID);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        SyncPreference pref = (SyncPreference) mPreferenceGroup.getPreference(0);
+        assertThat(pref.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_syncDisabled_summaryShouldBeSet() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Turns off automatic sync for the the sync adapter.
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ false,
+                mUserHandle.getIdentifier());
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        Preference pref = mPreferenceGroup.getPreference(0);
+        assertThat(pref.getSummary()).isEqualTo(mContext.getString(R.string.sync_disabled));
+    }
+
+    @Test
+    public void refreshUi_syncEnabled_activelySyncing_summaryShouldBeSet() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Turns on automatic sync for the the sync adapter.
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ true,
+                mUserHandle.getIdentifier());
+        // Adds the sync adapter to the list of currently syncing adapters.
+        SyncInfo syncInfo = new SyncInfo(/* authorityId= */ 0, mAccount, AUTHORITY, /* startTime= */
+                0);
+        List<SyncInfo> syncs = new ArrayList<>();
+        syncs.add(syncInfo);
+        ShadowContentResolver.setCurrentSyncs(syncs);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        Preference pref = mPreferenceGroup.getPreference(0);
+        assertThat(pref.getSummary()).isEqualTo(mContext.getString(R.string.sync_in_progress));
+    }
+
+    @Test
+    public void refreshUi_syncEnabled_syncHasHappened_summaryShouldBeSet() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Turns on automatic sync for the the sync adapter.
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ true,
+                mUserHandle.getIdentifier());
+        // Sets the sync adapter's last successful sync time.
+        SyncStatusInfo status = new SyncStatusInfo(0);
+        status.setLastSuccess(0, 83091);
+        ShadowContentResolver.setSyncStatus(mAccount, AUTHORITY, status);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        Preference pref = mPreferenceGroup.getPreference(0);
+
+        String expectedTimeString = mController.formatSyncDate(new Date(83091));
+        assertThat(pref.getSummary()).isEqualTo(
+                mContext.getString(R.string.last_synced, expectedTimeString));
+    }
+
+    @Test
+    public void refreshUi_activelySyncing_notInitialSync_shouldHaveActiveSyncIcon() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Adds the sync adapter to the list of currently syncing adapters.
+        SyncInfo syncInfo = new SyncInfo(/* authorityId= */ 0, mAccount, AUTHORITY, /* startTime= */
+                0);
+        List<SyncInfo> syncs = new ArrayList<>();
+        syncs.add(syncInfo);
+        ShadowContentResolver.setCurrentSyncs(syncs);
+        // Sets the sync adapter's initializing state to false (i.e. it's not performing an
+        // initial sync).
+        SyncStatusInfo status = new SyncStatusInfo(0);
+        status.initialize = false;
+        ShadowContentResolver.setSyncStatus(mAccount, AUTHORITY, status);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        Preference pref = mPreferenceGroup.getPreference(0);
+
+        assertThat(Shadows.shadowOf(pref.getIcon()).getCreatedFromResId()).isEqualTo(
+                R.drawable.ic_sync_anim);
+    }
+
+    @Test
+    public void refreshUi_syncPending_notInitialSync_shouldHaveActiveSyncIcon() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Sets the sync adapter's initializing state to false (i.e. it's not performing an
+        // initial sync).
+        // Also sets the the sync status to pending
+        SyncStatusInfo status = new SyncStatusInfo(0);
+        status.initialize = false;
+        status.pending = true;
+        ShadowContentResolver.setSyncStatus(mAccount, AUTHORITY, status);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        Preference pref = mPreferenceGroup.getPreference(0);
+
+        assertThat(Shadows.shadowOf(pref.getIcon()).getCreatedFromResId()).isEqualTo(
+                R.drawable.ic_sync);
+    }
+
+    @Test
+    public void refreshUi_syncFailed_shouldHaveProblemSyncIcon() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+        // Turns on automatic sync for the the sync adapter.
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ true,
+                mUserHandle.getIdentifier());
+        // Sets the sync adapter's last failure time and message so it appears to have failed
+        // previously.
+        SyncStatusInfo status = new SyncStatusInfo(0);
+        status.lastFailureTime = 10;
+        status.lastFailureMesg = "too-many-deletions";
+        ShadowContentResolver.setSyncStatus(mAccount, AUTHORITY, status);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        Preference pref = mPreferenceGroup.getPreference(0);
+
+        assertThat(Shadows.shadowOf(pref.getIcon()).getCreatedFromResId()).isEqualTo(
+                R.drawable.ic_sync_problem);
+    }
+
+    @Test
+    public void refreshUi_noSyncStatus_shouldHaveNoIcon() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        Preference pref = mPreferenceGroup.getPreference(0);
+
+        assertThat(pref.getIcon()).isNull();
+        assertThat(pref.isIconSpaceReserved()).isTrue();
+    }
+
+    @Test
+    public void onAccountsUpdate_correctUserId_shouldForceUpdatePreferences() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        ShadowContentResolver.reset();
+        mController.onAccountsUpdate(mUserHandle);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onAccountsUpdate_incorrectUserId_shouldNotForceUpdatePreferences() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        ShadowContentResolver.reset();
+        mController.onAccountsUpdate(new UserHandle(NOT_USER_ID));
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onSyncPreferenceClicked_preferenceUnchecked_shouldSetSyncAutomaticallyOff() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        // Turns off one time sync and turns on automatic sync for the adapter so the preference is
+        // checked.
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ true, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ true,
+                USER_ID);
+
+        mController.refreshUi();
+        SyncPreference pref = (SyncPreference) mPreferenceGroup.getPreference(0);
+        pref.performClick();
+
+        assertThat(ContentResolver.getSyncAutomaticallyAsUser(mAccount, AUTHORITY,
+                USER_ID)).isFalse();
+    }
+
+    @Test
+    public void onSyncPreferenceClicked_preferenceUnchecked_shouldCancelSync() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        // Turns off one time sync and turns on automatic sync for the adapter so the preference is
+        // checked.
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ true, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ true,
+                USER_ID);
+
+        mController.refreshUi();
+        SyncPreference pref = (SyncPreference) mPreferenceGroup.getPreference(0);
+        pref.performClick();
+
+        verify(mMockSyncListener).onSyncCanceled(eq(mAccount), eq(AUTHORITY), eq(USER_ID));
+    }
+
+    @Test
+    public void onSyncPreferenceClicked_preferenceChecked_shouldSetSyncAutomaticallyOn() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        // Turns off one time sync and automatic sync for the adapter so the preference is
+        // unchecked.
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ true, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ false,
+                USER_ID);
+
+        mController.refreshUi();
+        SyncPreference pref = (SyncPreference) mPreferenceGroup.getPreference(0);
+        pref.performClick();
+
+        assertThat(ContentResolver.getSyncAutomaticallyAsUser(mAccount, AUTHORITY,
+                USER_ID)).isTrue();
+    }
+
+    @Test
+    public void onSyncPreferenceClicked_preferenceChecked_masterSyncOff_shouldRequestSync() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        // Turns off master sync and automatic sync for the adapter so the preference is unchecked.
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ false, USER_ID);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, AUTHORITY, /* sync= */ false,
+                USER_ID);
+
+        mController.refreshUi();
+        SyncPreference pref = (SyncPreference) mPreferenceGroup.getPreference(0);
+
+        // Sets master sync off
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ false, USER_ID);
+        pref.performClick();
+
+        verify(mMockSyncListener).onSyncRequested(eq(mAccount), eq(AUTHORITY), eq(USER_ID),
+                any(Bundle.class));
+    }
+
+    @Test
+    public void onSyncPreferenceClicked_oneTimeSyncOn_shouldRequestSync() {
+        setUpVisibleSyncAdapters(AUTHORITY);
+
+        // Turns on one time sync mode
+        ContentResolver.setMasterSyncAutomaticallyAsUser(/* sync= */ false, USER_ID);
+
+        mController.refreshUi();
+        SyncPreference pref = (SyncPreference) mPreferenceGroup.getPreference(0);
+        pref.performClick();
+
+        verify(mMockSyncListener).onSyncRequested(eq(mAccount), eq(AUTHORITY), eq(USER_ID),
+                any(Bundle.class));
+    }
+
+    private void setUpVisibleSyncAdapters(String... authorities) {
+        SyncAdapterType[] syncAdapters = new SyncAdapterType[authorities.length];
+        for (int i = 0; i < authorities.length; i++) {
+            String authority = authorities[i];
+            // Adds a sync adapter type that has the right account type and is visible.
+            SyncAdapterType syncAdapterType = new SyncAdapterType(authority,
+                    ACCOUNT_TYPE, /* userVisible */ true, /* supportsUploading */ true);
+            syncAdapters[i] = syncAdapterType;
+
+            // Sets that the sync adapter is syncable.
+            ShadowContentResolver.setIsSyncable(mAccount, authority, /* syncable= */ SYNCABLE);
+
+            // Sets provider info with a label for the sync adapter.
+            ProviderInfo info = new ProviderInfo();
+            info.authority = authority;
+            info.name = authority;
+            // Set an application info to avoid an NPE
+            info.applicationInfo = new ApplicationInfo();
+            ProviderInfo[] providers = {info};
+
+            PackageInfo packageInfo = new PackageInfo();
+            packageInfo.packageName = authority;
+            packageInfo.providers = providers;
+            getShadowApplicationManager().addPackage(packageInfo);
+        }
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+    }
+
+    private ShadowApplicationPackageManager getShadowApplicationManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountSyncPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountSyncPreferenceControllerTest.java
new file mode 100644
index 0000000..aa940d1
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountSyncPreferenceControllerTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.SyncAdapterType;
+import android.os.UserHandle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * Unit test for {@link AccountSyncPreferenceController}.
+ *
+ * <p>Largely copied from {@link com.android.settings.accounts.AccountSyncPreferenceControllerTest}.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowContentResolver.class})
+public class AccountSyncPreferenceControllerTest {
+    private static final int SYNCABLE = 1;
+    private static final int NOT_SYNCABLE = 0;
+
+    private final Account mAccount = new Account("acct1", "type1");
+    private final int mUserId = 3;
+    private final UserHandle mUserHandle = new UserHandle(mUserId);
+
+    private AccountSyncPreferenceController mController;
+    private FragmentController mMockFragmentController;
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        PreferenceControllerTestHelper<AccountSyncPreferenceController> helper =
+                new PreferenceControllerTestHelper<>(application,
+                        AccountSyncPreferenceController.class);
+        mController = helper.getController();
+        mMockFragmentController = helper.getMockFragmentController();
+
+        mController.setAccount(mAccount);
+        mController.setUserHandle(mUserHandle);
+
+        mPreference = new Preference(application);
+        helper.setPreference(mPreference);
+        helper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void refreshUi_notSameAccountType_shouldNotCount() {
+        // Adds a sync adapter type that has a visible user, is syncable, and syncs automatically
+        // but does not have the right account type.
+        SyncAdapterType syncAdapterType = new SyncAdapterType("authority", /* accountType */
+                "type5", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        ContentResolver.setIsSyncable(mAccount, "authority", SYNCABLE);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount,
+                "authority", /* sync= */ true, /* userId= */ mUserId);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(application.getString(R.string.account_sync_summary_all_off));
+    }
+
+    @Test
+    public void refreshUi_adapterInvisible_shouldNotCount() {
+        // Adds a sync adapter type that has the right account type, is syncable, and syncs
+        // automatically, but doesn't have a visible user
+        SyncAdapterType syncAdapterType = new SyncAdapterType("authority",
+                /* accountType */ "type1", /* userVisible */ false, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        ContentResolver.setIsSyncable(mAccount, "authority", SYNCABLE);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, "authority", /* sync= */
+                true, /* userId= */ mUserId);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(application.getString(R.string.account_sync_summary_all_off));
+    }
+
+    @Test
+    public void refreshUi_notSyncable_shouldNotCount() {
+        // Adds a sync adapter type that is the right account type and a visible user, but is not
+        // syncable
+        SyncAdapterType syncAdapterType = new SyncAdapterType("authority", /* accountType */
+                "type1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        ContentResolver.setIsSyncable(mAccount, "authority", NOT_SYNCABLE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(application.getString(R.string.account_sync_summary_all_off));
+    }
+
+    @Test
+    public void refreshUi_masterAutomaticSyncIgnoredAndAccountSyncDisabled_shouldNotCount() {
+        // Adds a sync adapter type that is the right account type, has a visible user, and is
+        // syncable, but has master automatic sync ignored and account-level sync disabled
+        SyncAdapterType syncAdapterType = new SyncAdapterType("authority", /* accountType */
+                "type1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        ContentResolver.setIsSyncable(mAccount, "authority", SYNCABLE);
+        ContentResolver.setMasterSyncAutomaticallyAsUser(true, mUserId);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, "authority", /* sync= */
+                false, /* userId= */ mUserId);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(application.getString(R.string.account_sync_summary_all_off));
+    }
+
+    @Test
+    public void refreshUi_masterAutomaticSyncUsed_shouldCount() {
+        // Adds a sync adapter type that is the right account type, has a visible user, is
+        // syncable, and has master-level automatic syncing enabled
+        SyncAdapterType syncAdapterType = new SyncAdapterType("authority", /* accountType */
+                "type1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        ContentResolver.setIsSyncable(mAccount, "authority", SYNCABLE);
+        ContentResolver.setMasterSyncAutomaticallyAsUser(false, mUserId);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(application.getString(R.string.account_sync_summary_all_on));
+    }
+
+    @Test
+    public void refreshUi_automaticSyncEnabled_shouldCount() {
+        // Adds a sync adapter type that is the right account type, has a visible user, is
+        // syncable, and has account-level automatic syncing enabled
+        SyncAdapterType syncAdapterType = new SyncAdapterType("authority", /* accountType */
+                "type1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+        ContentResolver.setIsSyncable(mAccount, "authority", SYNCABLE);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, "authority", /* sync= */
+                true, /* userId= */ mUserId);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(application.getString(R.string.account_sync_summary_all_on));
+    }
+
+    @Test
+    public void refreshUi_someEnabled_shouldSetSummary() {
+        SyncAdapterType syncAdapterType1 = new SyncAdapterType("authority1", /* accountType */
+                "type1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType syncAdapterType2 = new SyncAdapterType("authority2", /* accountType */
+                "type1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType syncAdapterType3 = new SyncAdapterType("authority3", /* accountType */
+                "type1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType syncAdapterType4 = new SyncAdapterType("authority4", /* accountType */
+                "type1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters =
+                {syncAdapterType1, syncAdapterType2, syncAdapterType3, syncAdapterType4};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+
+        // Enable sync for the first three authorities and disable it for the fourth one
+        ContentResolver.setIsSyncable(mAccount, "authority1", SYNCABLE);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, "authority1", /* sync= */
+                true, /* userId= */ mUserId);
+
+        ContentResolver.setIsSyncable(mAccount, "authority2", SYNCABLE);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, "authority2", /* sync= */
+                true, /* userId= */ mUserId);
+
+        ContentResolver.setIsSyncable(mAccount, "authority3", SYNCABLE);
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, "authority3", /* sync= */
+                true, /* userId= */ mUserId);
+
+        ContentResolver.setSyncAutomaticallyAsUser(mAccount, "authority4", /* sync= */
+                false, /* userId= */ mUserId);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(application.getString(R.string.account_sync_summary_some_on, 3, 4));
+    }
+
+    @Test
+    public void handlePreferenceClicked_shouldLaunchAccountSyncDetailsFragment() {
+        mController.refreshUi();
+        mController.handlePreferenceClicked(mPreference);
+
+        verify(mMockFragmentController).launchFragment(any(AccountSyncDetailsFragment.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountsEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountsEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..4cc9d71
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/AccountsEntryPreferenceControllerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link AccountsEntryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class AccountsEntryPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private AccountsEntryPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+
+        mController = new PreferenceControllerTestHelper<>(RuntimeEnvironment.application,
+                AccountsEntryPreferenceController.class).getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_cannotModifyAccounts_disabledForUser() {
+        when(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_canModifyAccounts_available() {
+        when(mCarUserManagerHelper.canCurrentProcessModifyAccounts()).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/AccountsItemProviderTest.java b/tests/robotests/src/com/android/car/settings/accounts/AccountsItemProviderTest.java
deleted file mode 100644
index e74a161..0000000
--- a/tests/robotests/src/com/android/car/settings/accounts/AccountsItemProviderTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.accounts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.car.user.CarUserManagerHelper;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import com.android.car.settings.CarSettingsRobolectricTestRunner;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(CarSettingsRobolectricTestRunner.class)
-public class AccountsItemProviderTest {
-    @Mock
-    private AccountManagerHelper mAccountManagerHelper;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private AccountManager mAccountManager;
-    @Mock
-    private Context mContext;
-    @Mock
-    private CarUserManagerHelper mCarUserManagerHelper;
-
-    private AccountsItemProvider mProvider;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-        when(mContext.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager);
-        when(mContext.getApplicationContext()).thenReturn(mContext);
-        when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(new UserInfo());
-        when(mAccountManagerHelper.getAccountsForCurrentUser()).thenReturn(new ArrayList<>());
-
-        mCarUserManagerHelper = new CarUserManagerHelper(mContext);
-        mProvider = new AccountsItemProvider(
-                RuntimeEnvironment.application,
-                null,
-                mCarUserManagerHelper,
-                mAccountManagerHelper);
-    }
-
-    @Test
-    public void testAccountsAreSorted() {
-        Account account1 = new Account("g name", "g test type");
-        Account account2 = new Account("b name", "t test type");
-        Account account3 = new Account("a name", "g test type");
-        Account account4 = new Account("a name", "p test type");
-        List<Account> accounts = Arrays.asList(account1, account2, account3, account4);
-
-        when(mAccountManagerHelper.getAccountsForCurrentUser()).thenReturn(accounts);
-        when(mAccountManagerHelper.getLabelForType("g test type")).thenReturn("g test type");
-        when(mAccountManagerHelper.getLabelForType("t test type")).thenReturn("t test type");
-        when(mAccountManagerHelper.getLabelForType("p test type")).thenReturn("p test type");
-
-        assertThat(mProvider.getSortedUserAccounts())
-                .containsExactly(account3, account1, account4, account2).inOrder();
-    }
-
-}
diff --git a/tests/robotests/src/com/android/car/settings/accounts/ChooseAccountPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/accounts/ChooseAccountPreferenceControllerTest.java
new file mode 100644
index 0000000..b706ff7
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/accounts/ChooseAccountPreferenceControllerTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2018 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.car.settings.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Intent;
+import android.content.SyncAdapterType;
+import android.content.pm.UserInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ActivityResultCallback;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowContentResolver;
+
+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;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Unit tests for {@link ChooseAccountPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowContentResolver.class,
+        ShadowAccountManager.class})
+public class ChooseAccountPreferenceControllerTest {
+    private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
+    private static final int USER_ID = 0;
+
+    PreferenceControllerTestHelper<ChooseAccountPreferenceController> mHelper;
+    private PreferenceGroup mPreferenceGroup;
+    private ChooseAccountPreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mMockCarUserManagerHelper;
+
+    private AccountManager mAccountManager = AccountManager.get(application);
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Set up user info
+        ShadowCarUserManagerHelper.setMockInstance(mMockCarUserManagerHelper);
+        doReturn(new UserInfo(USER_ID, "name", 0)).when(
+                mMockCarUserManagerHelper).getCurrentProcessUserInfo();
+
+        // Add authenticated account types
+        addAuthenticator(/* type= */ "com.acct1", /* label= */ R.string.account_type1_label);
+        addAuthenticator(/* type= */ "com.acct2", /* label= */ R.string.account_type2_label);
+
+        mPreferenceGroup = new LogicalPreferenceGroup(application);
+        mHelper = new PreferenceControllerTestHelper<>(application,
+                ChooseAccountPreferenceController.class, mPreferenceGroup);
+        // Mark state as started so the AuthenticatorHelper listener is attached.
+        mHelper.markState(Lifecycle.State.STARTED);
+        mController = mHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void refreshUi_authenticatorPreferencesShouldBeSet() {
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        Preference acct1Pref = mPreferenceGroup.getPreference(0);
+        assertThat(acct1Pref.getTitle()).isEqualTo("Type 1");
+
+        Preference acct2Pref = mPreferenceGroup.getPreference(1);
+        assertThat(acct2Pref.getTitle()).isEqualTo("Type 2");
+    }
+
+    @Test
+    public void refreshUi_hasAccountTypeFilter_shouldFilterAccounts() {
+        // Add a filter that should filter out the second account type (com.acct2)
+        Set<String> accountTypesFilter = new HashSet<>();
+        accountTypesFilter.add("com.acct1");
+        mController.setAccountTypesFilter(accountTypesFilter);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        Preference acct1Pref = mPreferenceGroup.getPreference(0);
+        assertThat(acct1Pref.getTitle()).isEqualTo("Type 1");
+    }
+
+    @Test
+    public void refreshUi_hasAccountExclusionFilter_shouldFilterAccounts() {
+        // Add a filter that should filter out the first account type (com.acct1)
+        Set<String> accountExclusionTypesFilter = new HashSet<>();
+        accountExclusionTypesFilter.add("com.acct1");
+        mController.setAccountTypesExclusionFilter(accountExclusionTypesFilter);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        Preference acct1Pref = mPreferenceGroup.getPreference(0);
+        assertThat(acct1Pref.getTitle()).isEqualTo("Type 2");
+    }
+
+    @Test
+    public void refreshUi_doesNotHaveAuthoritiesInFilter_shouldNotBeShown() {
+        // Adds a sync adapter type for the com.acct1 account type that does not have the same
+        // authority as the one passed to someAuthority
+        SyncAdapterType syncAdapterType = new SyncAdapterType("someAuthority",
+                "com.acct1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+
+        mController.setAuthorities(Collections.singletonList("someOtherAuthority"));
+
+        // Force an authenticator refresh so the authorities are refreshed
+        mController.getAuthenticatorHelper().onReceive(application, null);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        Preference acct2Pref = mPreferenceGroup.getPreference(0);
+        assertThat(acct2Pref.getTitle()).isEqualTo("Type 2");
+    }
+
+    @Test
+    public void refreshUi_hasAuthoritiesInFilter_shouldBeShown() {
+        // Adds a sync adapter type for the com.acct1 account type that has the same authority as
+        // the one passed to someAuthority
+        SyncAdapterType syncAdapterType = new SyncAdapterType("someAuthority",
+                "com.acct1", /* userVisible */ true, /* supportsUploading */ true);
+        SyncAdapterType[] syncAdapters = {syncAdapterType};
+        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
+
+        mController.setAuthorities(Collections.singletonList("someAuthority"));
+
+        // Force an authenticator refresh so the authorities are refreshed
+        mController.getAuthenticatorHelper().onReceive(application, null);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        Preference acct1Pref = mPreferenceGroup.getPreference(0);
+        assertThat(acct1Pref.getTitle()).isEqualTo("Type 1");
+
+        Preference acct2Pref = mPreferenceGroup.getPreference(1);
+        assertThat(acct2Pref.getTitle()).isEqualTo("Type 2");
+    }
+
+    @Test
+    public void onAccountsUpdate_currentUserUpdated_shouldForceUpdate() {
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        addAuthenticator(/* type= */ "com.acct3", /* label= */ R.string.account_type3_label);
+
+        // Trigger an account update via the authenticator helper so that the state matches what
+        // it would be during actual execution.
+        mController.getAuthenticatorHelper().onReceive(application, null);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(3);
+        Preference acct3Pref = mPreferenceGroup.getPreference(2);
+        assertThat(acct3Pref.getTitle()).isEqualTo("Type 3");
+    }
+
+    @Test
+    public void onPreferenceClick_shouldStartActivity() {
+        Preference acct1Pref = mPreferenceGroup.getPreference(0);
+        acct1Pref.performClick();
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+
+        verify(mHelper.getMockFragmentController()).startActivityForResult(captor.capture(),
+                anyInt(), any(ActivityResultCallback.class));
+
+        Intent intent = captor.getValue();
+        assertThat(intent.getComponent().getClassName()).isEqualTo(
+                AddAccountActivity.class.getName());
+        assertThat(intent.getStringExtra(AddAccountActivity.EXTRA_SELECTED_ACCOUNT)).isEqualTo(
+                "com.acct1");
+    }
+
+    @Test
+    public void onAccountAdded_shouldGoBack() {
+        Preference acct1Pref = mPreferenceGroup.getPreference(0);
+        acct1Pref.performClick();
+
+        ArgumentCaptor<ActivityResultCallback> captor = ArgumentCaptor.forClass(
+                ActivityResultCallback.class);
+
+        verify(mHelper.getMockFragmentController()).startActivityForResult(any(Intent.class),
+                anyInt(), captor.capture());
+
+        ActivityResultCallback callback = captor.getValue();
+        callback.processActivityResult(ADD_ACCOUNT_REQUEST_CODE, /* resultCode= */ 0, /* data= */
+                null);
+
+        verify(mHelper.getMockFragmentController()).goBack();
+    }
+
+    private void addAuthenticator(String type, int labelRes) {
+        getShadowAccountManager().addAuthenticator(
+                new AuthenticatorDescription(type, "com.android.car.settings",
+                        labelRes, 0, 0, 0, false));
+    }
+
+    private ShadowAccountManager getShadowAccountManager() {
+        return Shadow.extract(mAccountManager);
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/car/settings/applications/AppPermissionsEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/AppPermissionsEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..031236c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/AppPermissionsEntryPreferenceControllerTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowPackageManager;
+
+/** Unit test for {@link AppPermissionsEntryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AppPermissionsEntryPreferenceControllerTest {
+
+    private Context mContext;
+    private Preference mPreference;
+    private AppPermissionsEntryPreferenceController mController;
+
+    private PermissionInfo mPermLocation;
+    private PermissionInfo mPermMic;
+    private PermissionInfo mPermCamera;
+    private PermissionInfo mPermSms;
+    private PermissionInfo mPermContacts;
+    private PermissionInfo mPermPhone;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        PreferenceControllerTestHelper<AppPermissionsEntryPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        AppPermissionsEntryPreferenceController.class, mPreference);
+        mController = controllerHelper.getController();
+        controllerHelper.markState(Lifecycle.State.STARTED);
+
+        mPermLocation = new PermissionInfo();
+        mPermLocation.name = "Permission1";
+        mPermLocation.group = "android.permission-group.LOCATION";
+        PermissionGroupInfo groupLocation = new PermissionGroupInfo();
+        groupLocation.name = mPermLocation.group;
+        groupLocation.nonLocalizedLabel = "Location";
+        getShadowPackageManager().addPermissionGroupInfo(groupLocation);
+
+        mPermMic = new PermissionInfo();
+        mPermMic.name = "Permission2";
+        mPermMic.group = "android.permission-group.MICROPHONE";
+        PermissionGroupInfo groupMic = new PermissionGroupInfo();
+        groupMic.name = mPermMic.group;
+        groupMic.nonLocalizedLabel = "Microphone";
+        getShadowPackageManager().addPermissionGroupInfo(groupMic);
+
+        mPermCamera = new PermissionInfo();
+        mPermCamera.name = "Permission3";
+        mPermCamera.group = "android.permission-group.CAMERA";
+        PermissionGroupInfo groupCamera = new PermissionGroupInfo();
+        groupCamera.name = mPermCamera.group;
+        groupCamera.nonLocalizedLabel = "Camera";
+        getShadowPackageManager().addPermissionGroupInfo(groupCamera);
+
+        mPermSms = new PermissionInfo();
+        mPermSms.name = "Permission4";
+        mPermSms.group = "android.permission-group.SMS";
+        PermissionGroupInfo groupSms = new PermissionGroupInfo();
+        groupSms.name = mPermSms.group;
+        groupSms.nonLocalizedLabel = "Sms";
+        getShadowPackageManager().addPermissionGroupInfo(groupSms);
+
+        mPermContacts = new PermissionInfo();
+        mPermContacts.name = "Permission5";
+        mPermContacts.group = "android.permission-group.CONTACTS";
+        PermissionGroupInfo groupContacts = new PermissionGroupInfo();
+        groupContacts.name = mPermContacts.group;
+        groupContacts.nonLocalizedLabel = "Contacts";
+        getShadowPackageManager().addPermissionGroupInfo(groupContacts);
+
+        mPermPhone = new PermissionInfo();
+        mPermPhone.name = "Permission6";
+        mPermPhone.group = "android.permission-group.PHONE";
+        PermissionGroupInfo groupPhone = new PermissionGroupInfo();
+        groupPhone.name = mPermPhone.group;
+        groupPhone.nonLocalizedLabel = "Phone";
+
+        getShadowPackageManager().addPermissionGroupInfo(groupPhone);
+    }
+
+
+    @Test
+    public void refreshUi_noGrantedPermissions_setsNullSummary() {
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isNull();
+    }
+
+    @Test
+    public void refreshUi_grantedPermissions_aboveMax_setsSummary_maxCountDefinedByController() {
+        setupPackageWithPermissions(mPermLocation, mPermMic, mPermCamera, mPermSms);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(
+                "Apps using location, microphone, and camera");
+    }
+
+    @Test
+    public void refreshUi_grantedPermissions_setsSummary_inOrderDefinedByController() {
+        setupPackageWithPermissions(mPermPhone, mPermMic, mPermContacts, mPermSms);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo("Apps using microphone, sms, and contacts");
+    }
+
+    @Test
+    public void refreshUi_grantedPermissions_onlyTwo_setsSummary() {
+        setupPackageWithPermissions(mPermLocation, mPermCamera);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo("Apps using location and camera");
+    }
+
+    @Test
+    public void refreshUi_grantedPermissions_onlyOne_setsSummary() {
+        setupPackageWithPermissions(mPermCamera);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo("Apps using camera");
+    }
+
+    private void setupPackageWithPermissions(PermissionInfo... permissions) {
+        PackageInfo info = new PackageInfo();
+        info.packageName = "fake.package.name";
+        info.permissions = permissions;
+        getShadowPackageManager().addPackage(info);
+    }
+
+    private ShadowPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/ApplicationDetailsFragmentTest.java b/tests/robotests/src/com/android/car/settings/applications/ApplicationDetailsFragmentTest.java
new file mode 100644
index 0000000..ffb5abe
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/ApplicationDetailsFragmentTest.java
@@ -0,0 +1,636 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.widget.Button;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowActivityManager;
+import com.android.car.settings.testutils.ShadowActivityThread;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowDevicePolicyManager;
+import com.android.car.settings.testutils.ShadowIconDrawableFactory;
+import com.android.car.settings.testutils.ShadowSmsApplication;
+import com.android.car.settings.testutils.ShadowUserManager;
+import com.android.car.settings.testutils.ShadowUtils;
+import com.android.settingslib.Utils;
+import com.android.settingslib.applications.ApplicationsState;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/** Unit test for {@link ApplicationDetailsFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {
+        ShadowActivityManager.class,
+        ShadowActivityThread.class,
+        ShadowApplicationPackageManager.class,
+        ShadowCarUserManagerHelper.class,
+        ShadowDevicePolicyManager.class,
+        ShadowIconDrawableFactory.class,
+        ShadowSmsApplication.class,
+        ShadowUserManager.class,
+        ShadowUtils.class})
+public class ApplicationDetailsFragmentTest {
+
+    private static final String PACKAGE_NAME = "com.android.car.settings.test";
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    private Context mContext;
+    private TestActivity mActivity;
+    private ActivityController<TestActivity> mController;
+    private ApplicationDetailsFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        int userId = UserHandle.myUserId();
+        UserInfo userInfo = new UserInfo();
+        userInfo.id = userId;
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(userId);
+        when(mCarUserManagerHelper.getAllUsers()).thenReturn(Collections.singletonList(userInfo));
+        UserManager mockUserManager = mock(UserManager.class);
+        when(mockUserManager.getUserInfo(userId)).thenReturn(userInfo);
+        ShadowUserManager.setInstance(mockUserManager);
+
+        mContext = RuntimeEnvironment.application;
+        getShadowUserManager().addProfile(userId, userId, "profileName", /* profileFlags= */ 0);
+
+        mActivity = new TestActivity();
+        mController = ActivityController.of(mActivity);
+        mController.create();
+
+        mFragment = ApplicationDetailsFragment.getInstance(PACKAGE_NAME);
+    }
+
+    @After
+    public void tearDown() {
+        // Prevent caching from interfering across tests.
+        ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", null);
+        ReflectionHelpers.setStaticField(Utils.class, "sSystemSignature", null);
+        ShadowApplicationPackageManager.reset();
+        ShadowCarUserManagerHelper.reset();
+        ShadowSmsApplication.reset();
+        ShadowUserManager.reset();
+        ShadowUtils.reset();
+    }
+
+    @Test
+    public void onStart_packageNotExplicitlyStopped_enablesForceStopButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findForceStopButton(mActivity).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onStart_packageHasActiveAdmins_disablesForceStopButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        getShadowDevicePolicyManager().setPackageHasActiveAdmins(
+                PACKAGE_NAME, /* hasActiveAdmins= */ true);
+        mController.start();
+
+        assertThat(findForceStopButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_appsControlUserRestriction_disablesForceStopButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_APPS_CONTROL)).thenReturn(true);
+        mController.start();
+
+        assertThat(findForceStopButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_packageExplicitlyStopped_queriesPackageRestart() {
+        int uid = 123;
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.uid = uid;
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
+
+        mController.start();
+
+        Intent broadcast = mActivity.getMostRecentOrderedBroadcastIntent();
+        assertThat(broadcast).isNotNull();
+        assertThat(broadcast.getAction()).isEqualTo(Intent.ACTION_QUERY_PACKAGE_RESTART);
+        assertThat(broadcast.getStringArrayExtra(Intent.EXTRA_PACKAGES)).isEqualTo(
+                new String[]{PACKAGE_NAME});
+        assertThat(broadcast.getIntExtra(Intent.EXTRA_UID, 0)).isEqualTo(uid);
+    }
+
+    @Test
+    public void onStart_packageExplicitlyStopped_success_enablesForceStopButton() {
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
+        mController.start();
+        BroadcastReceiver receiver = mActivity.getMostRecentOrderedBroadcastResultReceiver();
+        receiver.setPendingResult(
+                new BroadcastReceiver.PendingResult(Activity.RESULT_OK,
+                        "resultData",
+                        /* resultExtras= */ null,
+                        BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
+                        /* ordered= */ true,
+                        /* sticky= */ false,
+                        /* token= */ null,
+                        UserHandle.myUserId(),
+                        /* flags= */ 0));
+        receiver.onReceive(mContext, mActivity.getMostRecentOrderedBroadcastIntent());
+
+        assertThat(findForceStopButton(mActivity).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onStart_packageExplicitlyStopped_failure_disablesForceStopButton() {
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
+        mController.start();
+        BroadcastReceiver receiver = mActivity.getMostRecentOrderedBroadcastResultReceiver();
+        receiver.setPendingResult(
+                new BroadcastReceiver.PendingResult(Activity.RESULT_CANCELED,
+                        "resultData",
+                        /* resultExtras= */ null,
+                        BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
+                        /* ordered= */ true,
+                        /* sticky= */ false,
+                        /* token= */ null,
+                        UserHandle.myUserId(),
+                        /* flags= */ 0));
+        receiver.onReceive(mContext, mActivity.getMostRecentOrderedBroadcastIntent());
+
+        assertThat(findForceStopButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_bundledApp_showsDisableButton() {
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findDisableButton(mActivity).getText()).isEqualTo(
+                mContext.getString(R.string.disable_text));
+    }
+
+    @Test
+    public void onStart_bundledApp_notEnabled_showsEnableButton() {
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        packageInfo.applicationInfo.enabled = false;
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findDisableButton(mActivity).getText()).isEqualTo(
+                mContext.getString(R.string.enable_text));
+    }
+
+    @Test
+    public void onStart_bundledApp_enabled_disableUntilUsed_showsEnableButton() {
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        packageInfo.applicationInfo.enabledSetting =
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findDisableButton(mActivity).getText()).isEqualTo(
+                mContext.getString(R.string.enable_text));
+    }
+
+    @Test
+    public void onStart_bundledApp_homePackage_disablesDisableButton() {
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        getShadowPackageManager().addPackage(packageInfo);
+
+        ResolveInfo homeActivity = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = PACKAGE_NAME;
+        homeActivity.activityInfo = activityInfo;
+
+        getShadowPackageManager().setHomeActivities(Collections.singletonList(homeActivity));
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findDisableButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_bundledApp_systemPackage_disablesDisableButton() {
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        Signature[] signatures = new Signature[]{new Signature(PACKAGE_NAME.getBytes())};
+        packageInfo.signatures = signatures;
+        getShadowPackageManager().addPackage(packageInfo);
+
+        PackageInfo systemPackage = createPackageInfoWithApplicationInfo("android");
+        systemPackage.signatures = signatures;
+        getShadowPackageManager().addPackage(systemPackage);
+
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findDisableButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_bundledApp_enabledApp_keepEnabledPackage_disablesDisableButton() {
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+
+        ShadowSmsApplication.setDefaultSmsApplication(new ComponentName(PACKAGE_NAME, "cls"));
+        mController.start();
+
+        assertThat(findDisableButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_appNotBundled_showsUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).getText()).isEqualTo(
+                mContext.getString(R.string.uninstall_text));
+    }
+
+    @Test
+    public void onStart_packageHasActiveAdmins_disablesUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        getShadowDevicePolicyManager().setPackageHasActiveAdmins(
+                PACKAGE_NAME, /* hasActiveAdmins= */ true);
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_deviceProvisioningPackage_disablesUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        ShadowUtils.setDeviceProvisioningPackage(PACKAGE_NAME);
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_uninstallQueued_disablesUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        getShadowDevicePolicyManager().setIsUninstallInQueue(true);
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_noDefaultHome_onlyHomeApp_disablesUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+
+        ResolveInfo homeActivity = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = PACKAGE_NAME;
+        homeActivity.activityInfo = activityInfo;
+
+        getShadowPackageManager().setHomeActivities(Collections.singletonList(homeActivity));
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_noDefaultHome_multipleHomeApps_enablesUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+
+        ResolveInfo homeActivity = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = PACKAGE_NAME;
+        homeActivity.activityInfo = activityInfo;
+
+        ResolveInfo altHomeActivity = new ResolveInfo();
+        ActivityInfo altActivityInfo = new ActivityInfo();
+        altActivityInfo.packageName = PACKAGE_NAME + ".Someotherhome";
+        altHomeActivity.activityInfo = altActivityInfo;
+
+        getShadowPackageManager().setHomeActivities(Arrays.asList(homeActivity, altHomeActivity));
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onStart_defaultHomeApp_disablesUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+
+        ResolveInfo homeActivity = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = PACKAGE_NAME;
+        homeActivity.activityInfo = activityInfo;
+
+        ResolveInfo altHomeActivity = new ResolveInfo();
+        ActivityInfo altActivityInfo = new ActivityInfo();
+        altActivityInfo.packageName = PACKAGE_NAME + ".Someotherhome";
+        altHomeActivity.activityInfo = altActivityInfo;
+
+        getShadowPackageManager().setHomeActivities(Arrays.asList(homeActivity, altHomeActivity));
+        getShadowPackageManager().setDefaultHomeActivity(new ComponentName(PACKAGE_NAME, "cls"));
+        mActivity.launchFragment(mFragment);
+
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_appsControlUserRestriction_disablesUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_APPS_CONTROL)).thenReturn(true);
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStart_uninstallAppsRestriction_disablesUninstallButton() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_UNINSTALL_APPS)).thenReturn(true);
+        mController.start();
+
+        assertThat(findUninstallButton(mActivity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void forceStopClicked_showsDialog() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+        mController.start();
+
+        findForceStopButton(mActivity).performClick();
+
+        assertThat(mFragment.getFragmentManager().findFragmentByTag(
+                ApplicationDetailsFragment.FORCE_STOP_CONFIRM_DIALOG_TAG)).isInstanceOf(
+                ConfirmationDialogFragment.class);
+    }
+
+    @Test
+    public void forceStopDialogConfirmed_forceStopsPackage() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+        mController.start();
+        findForceStopButton(mActivity).performClick();
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+
+        dialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
+
+        assertThat(getShadowActivityManager().getMostRecentlyStoppedPackage()).isEqualTo(
+                PACKAGE_NAME);
+    }
+
+    @Test
+    public void disableClicked_showsDialog() {
+        // Use bundled app to get disable button.
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+        mController.start();
+
+        findDisableButton(mActivity).performClick();
+
+        assertThat(mFragment.getFragmentManager().findFragmentByTag(
+                ApplicationDetailsFragment.DISABLE_CONFIRM_DIALOG_TAG)).isInstanceOf(
+                ConfirmationDialogFragment.class);
+    }
+
+    @Test
+    public void disableDialogConfirmed_disablesPackage() {
+        // Use bundled app to get disable button.
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+        mController.start();
+        findDisableButton(mActivity).performClick();
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+
+        dialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
+
+        assertThat(
+                mContext.getPackageManager().getApplicationEnabledSetting(PACKAGE_NAME)).isEqualTo(
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
+    }
+
+    @Test
+    public void enableClicked_enablesPackage() {
+        // Use disabled bundled app to get enable button.
+        PackageInfo packageInfo = createPackageInfoWithApplicationInfo(PACKAGE_NAME);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        packageInfo.applicationInfo.enabled = false;
+        getShadowPackageManager().addPackage(packageInfo);
+        mActivity.launchFragment(mFragment);
+        mController.start();
+
+        findDisableButton(mActivity).performClick();
+
+        assertThat(
+                mContext.getPackageManager().getApplicationEnabledSetting(PACKAGE_NAME)).isEqualTo(
+                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+    }
+
+    @Test
+    public void uninstallClicked_startsUninstallActivity() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+        mController.start();
+
+        findUninstallButton(mActivity).performClick();
+
+        Intent intent = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_UNINSTALL_PACKAGE);
+        assertThat(intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false)).isTrue();
+        assertThat(intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)).isTrue();
+        assertThat(intent.getData().toString()).isEqualTo("package:" + PACKAGE_NAME);
+    }
+
+    @Test
+    public void processActivityResult_uninstall_resultOk_goesBack() {
+        getShadowPackageManager().addPackage(createPackageInfoWithApplicationInfo(PACKAGE_NAME));
+        mActivity.launchFragment(mFragment);
+        mController.start();
+        findUninstallButton(mActivity).performClick();
+
+        mFragment.processActivityResult(ApplicationDetailsFragment.UNINSTALL_REQUEST_CODE,
+                Activity.RESULT_OK, /* data= */ null);
+
+        assertThat(mActivity.getOnBackPressedFlag()).isTrue();
+    }
+
+    private Button findForceStopButton(Activity activity) {
+        return activity.findViewById(R.id.action_button2);
+    }
+
+    private Button findDisableButton(Activity activity) {
+        // Same button is used with a different handler.  This method is purely for readability.
+        return findUninstallButton(activity);
+    }
+
+    private Button findUninstallButton(Activity activity) {
+        return activity.findViewById(R.id.action_button1);
+    }
+
+    private PackageInfo createPackageInfoWithApplicationInfo(String packageName) {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = packageName;
+        packageInfo.applicationInfo = createApplicationInfo(packageName);
+        return packageInfo;
+    }
+
+    private ApplicationInfo createApplicationInfo(String packageName) {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = packageName;
+        applicationInfo.sourceDir =
+                RuntimeEnvironment.getTempDirectory()
+                        .createIfNotExists(applicationInfo.packageName + "-sourceDir")
+                        .toAbsolutePath()
+                        .toString();
+        return applicationInfo;
+    }
+
+    private ShadowUserManager getShadowUserManager() {
+        return Shadow.extract(UserManager.get(mContext));
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+
+    private ShadowDevicePolicyManager getShadowDevicePolicyManager() {
+        return Shadow.extract(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE));
+    }
+
+    private ShadowActivityManager getShadowActivityManager() {
+        return Shadow.extract(mContext.getSystemService(Context.ACTIVITY_SERVICE));
+    }
+
+    /** We extend the test activity here to add functionality that isn't useful elsewhere. */
+    private static class TestActivity extends BaseTestActivity {
+
+        private Intent mOrderedBroadcastIntent;
+        private BroadcastReceiver mOrderedBroadcastResultReceiver;
+
+        @Override
+        public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+                String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+                int initialCode, String initialData, Bundle initialExtras) {
+            mOrderedBroadcastIntent = intent;
+            mOrderedBroadcastResultReceiver = resultReceiver;
+        }
+
+        Intent getMostRecentOrderedBroadcastIntent() {
+            return mOrderedBroadcastIntent;
+        }
+
+        BroadcastReceiver getMostRecentOrderedBroadcastResultReceiver() {
+            return mOrderedBroadcastResultReceiver;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/ApplicationListItemManagerTest.java b/tests/robotests/src/com/android/car/settings/applications/ApplicationListItemManagerTest.java
new file mode 100644
index 0000000..78702cd
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/ApplicationListItemManagerTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.storage.VolumeInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+/** Unit test for {@link ApplicationListItemManager}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ApplicationListItemManagerTest {
+    private static final String LABEL = "label";
+    private static final String SIZE_STR = "12.34 MB";
+    private static final String SOURCE = "source";
+    private static final int UID = 12;
+
+    private Context mContext;
+    private ApplicationListItemManager mApplicationListItemManager;
+
+    @Mock
+    private VolumeInfo mVolumeInfo;
+    @Mock
+    private Lifecycle mLifecycle;
+    @Mock
+    private ApplicationsState mAppState;
+    @Mock
+    ApplicationsState.AppFilter mAppFilter;
+    @Mock
+    ApplicationListItemManager.AppListItemListener mAppListItemListener1;
+    @Mock
+    ApplicationListItemManager.AppListItemListener mAppListItemListener2;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mApplicationListItemManager = new ApplicationListItemManager(mVolumeInfo, mLifecycle,
+                mAppState);
+    }
+
+    @Test
+    public void startLoading_shouldStartNewSession() {
+        mApplicationListItemManager.startLoading(mAppFilter, /* param= */ null);
+
+        verify(mAppState).newSession(any(), eq(mLifecycle));
+    }
+
+    @Test
+    public void onRebuildComplete_shouldNotifyRegisteredListener() {
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.uid = UID;
+        appInfo.sourceDir = SOURCE;
+
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo,
+                1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        apps.add(appEntry);
+
+        mApplicationListItemManager.registerListener(mAppListItemListener1);
+        mApplicationListItemManager.registerListener(mAppListItemListener2);
+        mApplicationListItemManager.onRebuildComplete(apps);
+
+        verify(mAppListItemListener1).onDataLoaded(apps);
+        verify(mAppListItemListener2).onDataLoaded(apps);
+    }
+
+    @Test
+    public void onRebuildComplete_unRegisterOneListener_shouldNotifyRegisteredListener() {
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.uid = UID;
+        appInfo.sourceDir = SOURCE;
+
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo,
+                1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        apps.add(appEntry);
+
+        mApplicationListItemManager.registerListener(mAppListItemListener1);
+        mApplicationListItemManager.registerListener(mAppListItemListener2);
+        mApplicationListItemManager.unregisterlistener(mAppListItemListener2);
+        mApplicationListItemManager.onRebuildComplete(apps);
+
+        verify(mAppListItemListener1).onDataLoaded(apps);
+        verify(mAppListItemListener2, times(0)).onDataLoaded(apps);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/ApplicationPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/ApplicationPreferenceControllerTest.java
new file mode 100644
index 0000000..ecfd20a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/ApplicationPreferenceControllerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ApplicationPreferenceControllerTest {
+    private static final String PACKAGE_NAME = "Test Package Name";
+
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<ApplicationPreferenceController>
+            mPreferenceControllerHelper;
+    private ApplicationPreferenceController mController;
+    @Mock
+    private ApplicationsState mMockAppState;
+    @Mock
+    private ApplicationsState.AppEntry mMockAppEntry;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = spy(RuntimeEnvironment.application);
+        mMockAppEntry.label = PACKAGE_NAME;
+
+        mPreference = new Preference(context);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
+                ApplicationPreferenceController.class);
+        mController = mPreferenceControllerHelper.getController();
+    }
+
+    @Test
+    public void testCheckInitialized_noAppState_throwException() {
+        mController.setAppEntry(mMockAppEntry);
+        assertThrows(IllegalStateException.class,
+                () -> mPreferenceControllerHelper.setPreference(mPreference));
+    }
+
+    @Test
+    public void testCheckInitialized_noAppEntry_throwException() {
+        mController.setAppState(mMockAppState);
+        assertThrows(IllegalStateException.class,
+                () -> mPreferenceControllerHelper.setPreference(mPreference));
+    }
+
+    @Test
+    public void testRefreshUi_hasResolveInfo_setTitle() {
+        mController.setAppEntry(mMockAppEntry);
+        mController.setAppState(mMockAppState);
+        mPreferenceControllerHelper.setPreference(mPreference);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+        assertThat(mPreference.getTitle()).isEqualTo(PACKAGE_NAME);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/ApplicationsSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/ApplicationsSettingsPreferenceControllerTest.java
new file mode 100644
index 0000000..ed26ea1
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/ApplicationsSettingsPreferenceControllerTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+/** Unit test for {@link ApplicationsSettingsPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ApplicationsSettingsPreferenceControllerTest {
+
+    private static final String SOURCE = "source";
+    private static final int UID = 12;
+    private static final String LABEL = "label";
+    private static final String SIZE_STR = "12.34 MB";
+
+    private Context mContext;
+    private LogicalPreferenceGroup mLogicalPreferenceGroup;
+    private PreferenceControllerTestHelper<ApplicationsSettingsPreferenceController>
+            mPreferenceControllerHelper;
+    private ApplicationsSettingsPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mLogicalPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                ApplicationsSettingsPreferenceController.class, mLogicalPreferenceGroup);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void defaultInitialize_hasNoPreference() {
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onDataLoaded_addPreference_hasOnePreference() {
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.uid = UID;
+        appInfo.sourceDir = SOURCE;
+
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo,
+                1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        apps.add(appEntry);
+
+        mController.onDataLoaded(apps);
+
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getTitle()).isEqualTo(LABEL);
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getSummary()).isEqualTo(SIZE_STR);
+    }
+
+    @Test
+    public void preferenceClick_launchesDetailFragment() {
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.uid = UID;
+        appInfo.sourceDir = SOURCE;
+
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo,
+                1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        apps.add(appEntry);
+
+        mController.onDataLoaded(apps);
+
+        Preference preference = mLogicalPreferenceGroup.getPreference(0);
+        preference.performClick();
+
+        verify(mPreferenceControllerHelper.getMockFragmentController()).launchFragment(
+                any(ApplicationDetailsFragment.class));
+    }
+
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/ApplicationsUtilsTest.java b/tests/robotests/src/com/android/car/settings/applications/ApplicationsUtilsTest.java
new file mode 100644
index 0000000..a5b5b51
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/ApplicationsUtilsTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.pm.UserInfo;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowDefaultDialerManager;
+import com.android.car.settings.testutils.ShadowSmsApplication;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Collections;
+
+/** Unit test for {@link ApplicationsUtils}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowDefaultDialerManager.class, ShadowSmsApplication.class})
+public class ApplicationsUtilsTest {
+
+    private static final String PACKAGE_NAME = "com.android.car.settings.test";
+
+    @After
+    public void tearDown() {
+        ShadowDefaultDialerManager.reset();
+        ShadowSmsApplication.reset();
+    }
+
+    @Test
+    public void isKeepEnabledPackage_defaultDialerApplication_returnsTrue() {
+        ShadowDefaultDialerManager.setDefaultDialerApplication(PACKAGE_NAME);
+
+        assertThat(ApplicationsUtils.isKeepEnabledPackage(RuntimeEnvironment.application,
+                PACKAGE_NAME)).isTrue();
+    }
+
+    @Test
+    public void isKeepEnabledPackage_defaultSmsApplication_returnsTrue() {
+        ShadowSmsApplication.setDefaultSmsApplication(new ComponentName(PACKAGE_NAME, "cls"));
+
+        assertThat(ApplicationsUtils.isKeepEnabledPackage(RuntimeEnvironment.application,
+                PACKAGE_NAME)).isTrue();
+    }
+
+    @Test
+    public void isKeepEnabledPackage_returnsFalse() {
+        assertThat(ApplicationsUtils.isKeepEnabledPackage(RuntimeEnvironment.application,
+                PACKAGE_NAME)).isFalse();
+    }
+
+    @Test
+    public void isProfileOrDeviceOwner_profileOwner_returnsTrue() {
+        UserInfo userInfo = new UserInfo();
+        userInfo.id = 123;
+        DevicePolicyManager dpm = mock(DevicePolicyManager.class);
+        CarUserManagerHelper um = mock(CarUserManagerHelper.class);
+        when(um.getAllUsers()).thenReturn(Collections.singletonList(userInfo));
+        when(dpm.getProfileOwnerAsUser(userInfo.id)).thenReturn(
+                new ComponentName(PACKAGE_NAME, "cls"));
+
+        assertThat(ApplicationsUtils.isProfileOrDeviceOwner(PACKAGE_NAME, dpm, um)).isTrue();
+    }
+
+    @Test
+    public void isProfileOrDeviceOwner_deviceOwner_returnsTrue() {
+        DevicePolicyManager dpm = mock(DevicePolicyManager.class);
+        when(dpm.isDeviceOwnerAppOnAnyUser(PACKAGE_NAME)).thenReturn(true);
+
+        assertThat(ApplicationsUtils.isProfileOrDeviceOwner(PACKAGE_NAME, dpm,
+                mock(CarUserManagerHelper.class))).isTrue();
+    }
+
+    @Test
+    public void isProfileOrDeviceOwner_returnsFalse() {
+        assertThat(ApplicationsUtils.isProfileOrDeviceOwner(PACKAGE_NAME,
+                mock(DevicePolicyManager.class), mock(CarUserManagerHelper.class))).isFalse();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java
new file mode 100644
index 0000000..90630ea
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class NotificationsPreferenceControllerTest {
+    private static final String PKG_NAME = "package.name";
+    private static final int UID = 1001010;
+    private Context mContext;
+    private NotificationsPreferenceController mController;
+    private PreferenceControllerTestHelper<NotificationsPreferenceController>
+            mPreferenceControllerHelper;
+    private TwoStatePreference mTwoStatePreference;
+    @Mock
+    private INotificationManager mMockManager;
+    @Mock
+    private NotificationChannel mMockChannel;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mTwoStatePreference = new SwitchPreference(mContext);
+
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                NotificationsPreferenceController.class, mTwoStatePreference);
+        mController = mPreferenceControllerHelper.getController();
+        mController.mNotificationManager = mMockManager;
+
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = PKG_NAME;
+
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = PKG_NAME;
+        packageInfo.applicationInfo = applicationInfo;
+        packageInfo.applicationInfo.uid = UID;
+        mController.setPackageInfo(packageInfo);
+    }
+
+    @Test
+    public void onCreate_notificationEnabled_isChecked() throws Exception {
+        when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(true);
+
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mTwoStatePreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onCreate_notificationDisabled_isNotChecked() throws Exception {
+        when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(false);
+
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mTwoStatePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void callChangeListener_setEnable_enablingNotification() throws Exception {
+        when(mMockManager.onlyHasDefaultChannel(PKG_NAME, UID)).thenReturn(false);
+
+        mTwoStatePreference.callChangeListener(true);
+
+        verify(mMockManager).setNotificationsEnabledForPackage(PKG_NAME, UID, true);
+    }
+
+    @Test
+    public void callChangeListener_setDisable_disablingNotification() throws Exception {
+        when(mMockManager.onlyHasDefaultChannel(PKG_NAME, UID)).thenReturn(false);
+
+        mTwoStatePreference.callChangeListener(false);
+
+        verify(mMockManager).setNotificationsEnabledForPackage(PKG_NAME, UID, false);
+    }
+
+    @Test
+    public void callChangeListener_onlyHasDefaultChannel_updateChannel() throws Exception {
+        when(mMockManager.onlyHasDefaultChannel(PKG_NAME, UID)).thenReturn(true);
+        when(mMockManager
+                .getNotificationChannelForPackage(
+                        PKG_NAME, UID, NotificationChannel.DEFAULT_CHANNEL_ID, true))
+                .thenReturn(mMockChannel);
+
+        mTwoStatePreference.callChangeListener(true);
+
+        verify(mMockManager).updateNotificationChannelForPackage(PKG_NAME, UID, mMockChannel);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/PermissionsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/PermissionsPreferenceControllerTest.java
new file mode 100644
index 0000000..8741417
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/PermissionsPreferenceControllerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class PermissionsPreferenceControllerTest {
+
+    private static final String PACKAGE_NAME = "Test Package Name";
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<PermissionsPreferenceController>
+            mPreferenceControllerHelper;
+    private PermissionsPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                PermissionsPreferenceController.class);
+        mController = mPreferenceControllerHelper.getController();
+        mPreference = new Preference(mContext);
+    }
+
+    @Test
+    public void testCheckInitialized_noResolveInfo_throwException() {
+        assertThrows(IllegalStateException.class,
+                () -> mPreferenceControllerHelper.setPreference(mPreference));
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_navigateToNextActivity() {
+        // Setup so the controller knows about the preference.
+        mController.setPackageName(PACKAGE_NAME);
+        mPreferenceControllerHelper.setPreference(mPreference);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        assertThat(mController.handlePreferenceClicked(mPreference)).isTrue();
+
+        Intent actual = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(actual.getAction()).isEqualTo(Intent.ACTION_MANAGE_APP_PERMISSIONS);
+        assertThat(actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE_NAME);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/VersionPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/VersionPreferenceControllerTest.java
new file mode 100644
index 0000000..aa53c6c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/VersionPreferenceControllerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 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.car.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class VersionPreferenceControllerTest {
+    private static final String TEST_VERSION_NAME = "9";
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<VersionPreferenceController> mPreferenceControllerHelper;
+    private VersionPreferenceController mController;
+    private PackageInfo mPackageInfo;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                VersionPreferenceController.class);
+        mController = mPreferenceControllerHelper.getController();
+        mPreference = new Preference(mContext);
+
+        mPackageInfo = new PackageInfo();
+        mPackageInfo.versionName = TEST_VERSION_NAME;
+    }
+
+    @Test
+    public void testCheckInitialized_noPackageInfo_throwException() {
+        assertThrows(IllegalStateException.class,
+                () -> mPreferenceControllerHelper.setPreference(mPreference));
+    }
+
+    @Test
+    public void testRefreshUi_hasPackageInfo_setTitle() {
+        mController.setPackageInfo(mPackageInfo);
+        mPreferenceControllerHelper.setPreference(mPreference);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+        assertThat(mPreference.getTitle()).isEqualTo(
+                mContext.getString(R.string.application_version_label, TEST_VERSION_NAME));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/assist/AssistConfigBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/assist/AssistConfigBasePreferenceControllerTest.java
new file mode 100644
index 0000000..5b99a3e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/assist/AssistConfigBasePreferenceControllerTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+
+import com.google.common.collect.Iterables;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowSecureSettings.class})
+public class AssistConfigBasePreferenceControllerTest {
+
+    private static class TestAssistConfigBasePreferenceController extends
+            AssistConfigBasePreferenceController {
+
+        private int mNumCallsToUpdateState;
+
+        TestAssistConfigBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+            mNumCallsToUpdateState = 0;
+        }
+
+        public int getNumCallsToUpdateState() {
+            return mNumCallsToUpdateState;
+        }
+
+        @Override
+        protected void updateState(TwoStatePreference preference) {
+            mNumCallsToUpdateState++;
+        }
+
+        @Override
+        protected List<Uri> getSettingUris() {
+            return Collections.singletonList(
+                    Settings.Secure.getUriFor(Settings.Secure.ASSIST_STRUCTURE_ENABLED));
+        }
+    }
+
+    private static final int TEST_USER_ID = 10;
+    private static final String TEST_PACKAGE_NAME = "com.test.package";
+    private static final String TEST_SERVICE = "TestService";
+
+    private Context mContext;
+    private TwoStatePreference mTwoStatePreference;
+    private PreferenceControllerTestHelper<TestAssistConfigBasePreferenceController>
+            mControllerHelper;
+    private TestAssistConfigBasePreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER_ID);
+
+        mContext = RuntimeEnvironment.application;
+        mTwoStatePreference = new SwitchPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestAssistConfigBasePreferenceController.class, mTwoStatePreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowSecureSettings.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_hasAssistComponent_isAvailable() {
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                key, TEST_USER_ID);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noAssistComponent_conditionallyUnavailable() {
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void onStart_registersObserver() {
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                key, TEST_USER_ID);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(getShadowContentResolver().getContentObservers(
+                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT))).isNotEmpty();
+        assertThat(getShadowContentResolver().getContentObservers(
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_STRUCTURE_ENABLED))).isNotEmpty();
+    }
+
+    @Test
+    public void onStop_unregistersObserver() {
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                key, TEST_USER_ID);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(getShadowContentResolver().getContentObservers(
+                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT))).isEmpty();
+        assertThat(getShadowContentResolver().getContentObservers(
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_STRUCTURE_ENABLED))).isEmpty();
+    }
+
+    @Test
+    public void onChange_changeRegisteredSetting_callsRefreshUi() {
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                key, TEST_USER_ID);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        int currentCount = mController.getNumCallsToUpdateState();
+
+        ContentObserver observer = Iterables.get(getShadowContentResolver().getContentObservers(
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_STRUCTURE_ENABLED)), 0);
+        observer.onChange(/* selfChange= */ false,
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_STRUCTURE_ENABLED));
+
+        assertThat(mController.getNumCallsToUpdateState()).isEqualTo(currentCount + 1);
+    }
+
+    private ShadowContentResolver getShadowContentResolver() {
+        return (ShadowContentResolver) Shadows.shadowOf(mContext.getContentResolver());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..2c22add
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerEntryPreferenceControllerTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import static com.android.car.settings.common.PreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
+import android.provider.Settings;
+import android.service.voice.VoiceInteractionServiceInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+import com.android.car.settings.testutils.ShadowVoiceInteractionServiceInfo;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import com.google.common.collect.Iterables;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowApplicationPackageManager;
+import org.robolectric.shadows.ShadowContentResolver;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class, ShadowCarUserManagerHelper.class,
+        ShadowVoiceInteractionServiceInfo.class})
+public class DefaultVoiceInputPickerEntryPreferenceControllerTest {
+
+    private static final String TEST_PACKAGE = "com.android.car.settings.testutils";
+    private static final String TEST_ASSIST = "TestAssistService";
+    private static final String TEST_VOICE = "TestVoiceService";
+    private static final String TEST_SETTINGS_CLASS = "TestSettingsActivity";
+    private static final int TEST_USER_ID = 10;
+
+    private Context mContext;
+    private DefaultVoiceInputPickerEntryPreferenceController mController;
+    private PreferenceControllerTestHelper<DefaultVoiceInputPickerEntryPreferenceController>
+            mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        CarUserManagerHelper carUserManagerHelper = mock(CarUserManagerHelper.class);
+        ShadowCarUserManagerHelper.setMockInstance(carUserManagerHelper);
+
+        mContext = RuntimeEnvironment.application;
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DefaultVoiceInputPickerEntryPreferenceController.class,
+                new ButtonPreference(mContext));
+        mController = mControllerHelper.getController();
+
+        // Set user.
+        when(carUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER_ID);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowSecureSettings.reset();
+        ShadowVoiceInteractionServiceInfo.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_sameComponents_returnsConditionallyUnavailable() {
+        setCurrentAssistant(new ComponentName(TEST_PACKAGE, TEST_VOICE));
+        setCurrentVoiceService(new ComponentName(TEST_PACKAGE, TEST_VOICE));
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(
+                CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_bothNull_returnsConditionallyUnavailable() {
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(
+                CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_differentComponents_returnsAvailable() {
+        setCurrentAssistant(new ComponentName(TEST_PACKAGE, TEST_ASSIST));
+        setCurrentVoiceService(new ComponentName(TEST_PACKAGE, TEST_VOICE));
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(
+                PreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_assistNull_returnsAvailable() {
+        setCurrentVoiceService(new ComponentName(TEST_PACKAGE, TEST_VOICE));
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(
+                PreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_voiceInputNull_returnsAvailable() {
+        setCurrentAssistant(new ComponentName(TEST_PACKAGE, TEST_ASSIST));
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(
+                PreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void onStart_registersObserver() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(getShadowContentResolver().getContentObservers(
+                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT))).isNotEmpty();
+    }
+
+    @Test
+    public void onStop_unregistersObserver() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(getShadowContentResolver().getContentObservers(
+                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT))).isEmpty();
+    }
+
+    @Test
+    public void onChange_changeRegisteredSetting_callsRefreshUi() {
+        setCurrentVoiceService(new ComponentName(TEST_PACKAGE, TEST_VOICE));
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(PreferenceController.AVAILABLE);
+
+        setCurrentAssistant(new ComponentName(TEST_PACKAGE, TEST_VOICE));
+        ContentObserver observer = Iterables.get(getShadowContentResolver().getContentObservers(
+                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT)), 0);
+        observer.onChange(/* selfChange= */ false,
+                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT));
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(
+                PreferenceController.CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getCurrentDefaultAppInfo_providerHasCurrentService_returnsValidDefaultAppInfo() {
+        // This is used so tht the VoiceInputInfoProvider returns a valid service.
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE;
+        resolveInfo.serviceInfo.name = TEST_VOICE;
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, resolveInfo);
+
+        // Create new controller to rerun the constructor with the new shadow package manager.
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DefaultVoiceInputPickerEntryPreferenceController.class,
+                new ButtonPreference(mContext));
+        mController = mControllerHelper.getController();
+
+        ComponentName voiceService = new ComponentName(TEST_PACKAGE, TEST_VOICE);
+        setCurrentVoiceService(voiceService);
+
+        assertThat(mController.getCurrentDefaultAppInfo()).isNotNull();
+    }
+
+    @Test
+    public void getCurrentDefaultAppInfo_providerHasNoService_returnsNull() {
+        assertThat(mController.getCurrentDefaultAppInfo()).isNull();
+    }
+
+    @Test
+    public void getSettingIntent_nullInput_returnsNull() {
+        assertThat(mController.getSettingIntent(null)).isEqualTo(null);
+    }
+
+    @Test
+    public void getSettingIntent_inputIsWrongType_returnsNull() {
+        DefaultAppInfo info = mock(DefaultAppInfo.class);
+        assertThat(mController.getSettingIntent(info)).isEqualTo(null);
+    }
+
+    @Test
+    public void getSettingIntent_validInput_returnsIntent() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE;
+        resolveInfo.serviceInfo.name = TEST_VOICE;
+
+        ShadowVoiceInteractionServiceInfo.setSettingsActivity(resolveInfo.serviceInfo,
+                TEST_SETTINGS_CLASS);
+        VoiceInteractionServiceInfo interactionServiceInfo = new VoiceInteractionServiceInfo(
+                mContext.getPackageManager(), resolveInfo.serviceInfo);
+        VoiceInputInfoProvider.VoiceInputInfo info =
+                new VoiceInputInfoProvider.VoiceInteractionInfo(mContext, interactionServiceInfo);
+
+        DefaultVoiceInputServiceInfo serviceInfo = new DefaultVoiceInputServiceInfo(mContext,
+                new PackageManagerWrapper(mContext.getPackageManager()), TEST_USER_ID, info, true);
+        Intent settingIntent = mController.getSettingIntent(serviceInfo);
+
+        assertThat(settingIntent.getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(settingIntent.getComponent()).isEqualTo(
+                new ComponentName(TEST_PACKAGE, TEST_SETTINGS_CLASS));
+    }
+
+    private void setCurrentVoiceService(ComponentName service) {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, service.flattenToString());
+    }
+
+    private void setCurrentAssistant(ComponentName assist) {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                assist.flattenToString(), TEST_USER_ID);
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+
+    private ShadowContentResolver getShadowContentResolver() {
+        return (ShadowContentResolver) Shadows.shadowOf(mContext.getContentResolver());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerPreferenceControllerTest.java
new file mode 100644
index 0000000..84e8572
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/assist/DefaultVoiceInputPickerPreferenceControllerTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.provider.Settings;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+import com.android.car.settings.testutils.ShadowVoiceInteractionServiceInfo;
+import com.android.settingslib.applications.DefaultAppInfo;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowApplicationPackageManager;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class, ShadowVoiceInteractionServiceInfo.class,
+        ShadowCarUserManagerHelper.class})
+public class DefaultVoiceInputPickerPreferenceControllerTest {
+
+    private static final String TEST_PACKAGE_NAME = "com.test.package";
+    private static final String TEST_SERVICE = "TestService";
+    private static final String TEST_OTHER_SERVICE = "TestOtherService";
+    private static final String TEST_RECOGNIZER = "TestRecognizer";
+    private static final int TEST_USER_ID = 10;
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<DefaultVoiceInputPickerPreferenceController>
+            mControllerHelper;
+    private DefaultVoiceInputPickerPreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+
+        mContext = RuntimeEnvironment.application;
+
+        // Set user.
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER_ID);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSecureSettings.reset();
+        ShadowCarUserManagerHelper.reset();
+        ShadowVoiceInteractionServiceInfo.reset();
+    }
+
+    @Test
+    public void getCandidates_voiceInteractionService_hasOneElement() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        resolveInfo.serviceInfo.name = TEST_SERVICE;
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, resolveInfo);
+        setupController();
+
+        assertThat(mController.getCandidates()).hasSize(1);
+    }
+
+    @Test
+    public void getCandidates_voiceRecognitionService_hasOneElement() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        resolveInfo.serviceInfo.name = TEST_RECOGNIZER;
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_RECOGNITION_SERVICE_TAG, resolveInfo);
+        setupController();
+
+        assertThat(mController.getCandidates()).hasSize(1);
+    }
+
+    @Test
+    public void getCandidates_oneIsSameAsAssistant_hasTwoElements() {
+        ResolveInfo interactionInfo = new ResolveInfo();
+        interactionInfo.serviceInfo = new ServiceInfo();
+        interactionInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        interactionInfo.serviceInfo.name = TEST_SERVICE;
+        interactionInfo.serviceInfo.applicationInfo = new ApplicationInfo();
+        interactionInfo.serviceInfo.applicationInfo.nonLocalizedLabel = "1";
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, interactionInfo);
+
+        ResolveInfo interactionInfo2 = new ResolveInfo();
+        interactionInfo2.serviceInfo = new ServiceInfo();
+        interactionInfo2.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        interactionInfo2.serviceInfo.name = TEST_OTHER_SERVICE;
+        interactionInfo2.serviceInfo.applicationInfo = new ApplicationInfo();
+        interactionInfo2.serviceInfo.applicationInfo.nonLocalizedLabel = "2";
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, interactionInfo2);
+
+        ComponentName voiceInteraction = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE);
+        setCurrentAssistant(voiceInteraction);
+        setCurrentVoiceInteractionService(voiceInteraction);
+
+        setupController();
+
+        assertThat(mController.getCandidates()).hasSize(2);
+    }
+
+    @Test
+    public void getCandidates_oneIsSameAsAssistant_sameOneIsEnabled() {
+        ResolveInfo interactionInfo = new ResolveInfo();
+        interactionInfo.serviceInfo = new ServiceInfo();
+        interactionInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        interactionInfo.serviceInfo.name = TEST_SERVICE;
+        interactionInfo.serviceInfo.applicationInfo = new ApplicationInfo();
+        interactionInfo.serviceInfo.applicationInfo.nonLocalizedLabel = "1";
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, interactionInfo);
+
+        ResolveInfo interactionInfo2 = new ResolveInfo();
+        interactionInfo2.serviceInfo = new ServiceInfo();
+        interactionInfo2.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        interactionInfo2.serviceInfo.name = TEST_OTHER_SERVICE;
+        interactionInfo2.serviceInfo.applicationInfo = new ApplicationInfo();
+        interactionInfo2.serviceInfo.applicationInfo.nonLocalizedLabel = "2";
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, interactionInfo2);
+
+        ComponentName voiceInteraction = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE);
+        setCurrentAssistant(voiceInteraction);
+        setCurrentVoiceInteractionService(voiceInteraction);
+
+        setupController();
+
+        DefaultAppInfo defaultAppInfo = null;
+        for (DefaultAppInfo info : mController.getCandidates()) {
+            if (info.componentName.equals(new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE))) {
+                defaultAppInfo = info;
+            }
+        }
+        assertThat(defaultAppInfo).isNotNull();
+        assertThat(defaultAppInfo.enabled).isTrue();
+    }
+
+    @Test
+    public void getCandidates_oneIsSameAsAssistant_differentOneIsDisabled() {
+        ResolveInfo interactionInfo = new ResolveInfo();
+        interactionInfo.serviceInfo = new ServiceInfo();
+        interactionInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        interactionInfo.serviceInfo.name = TEST_SERVICE;
+        interactionInfo.serviceInfo.applicationInfo = new ApplicationInfo();
+        interactionInfo.serviceInfo.applicationInfo.nonLocalizedLabel = "1";
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, interactionInfo);
+
+        ResolveInfo interactionInfo2 = new ResolveInfo();
+        interactionInfo2.serviceInfo = new ServiceInfo();
+        interactionInfo2.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        interactionInfo2.serviceInfo.name = TEST_OTHER_SERVICE;
+        interactionInfo2.serviceInfo.applicationInfo = new ApplicationInfo();
+        interactionInfo2.serviceInfo.applicationInfo.nonLocalizedLabel = "2";
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, interactionInfo2);
+
+        ComponentName voiceInteraction = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE);
+        setCurrentAssistant(voiceInteraction);
+        setCurrentVoiceInteractionService(voiceInteraction);
+
+        setupController();
+
+        DefaultAppInfo defaultAppInfo = null;
+        for (DefaultAppInfo info : mController.getCandidates()) {
+            if (info.componentName.equals(
+                    new ComponentName(TEST_PACKAGE_NAME, TEST_OTHER_SERVICE))) {
+                defaultAppInfo = info;
+            }
+        }
+        assertThat(defaultAppInfo).isNotNull();
+        assertThat(defaultAppInfo.enabled).isFalse();
+    }
+
+    @Test
+    public void getCurrentDefaultKey_defaultIsNull_returnsNull() {
+        setupController();
+
+        assertThat(mController.getCurrentDefaultKey()).isNull();
+    }
+
+    @Test
+    public void getCurrentDefaultKey_defaultExists_returnsComponentName() {
+        setupController();
+
+        ComponentName cn = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE);
+        setCurrentVoiceInteractionService(cn);
+
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(cn.flattenToString());
+    }
+
+    @Test
+    public void setCurrentDefault_typeVoiceInteractionInfo_setsServices() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        resolveInfo.serviceInfo.name = TEST_SERVICE;
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, resolveInfo);
+        ShadowVoiceInteractionServiceInfo.setRecognitionService(resolveInfo.serviceInfo,
+                TEST_RECOGNIZER);
+        setupController();
+
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        String recognizer = new ComponentName(TEST_PACKAGE_NAME, TEST_RECOGNIZER).flattenToString();
+        mController.setCurrentDefault(key);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE)).isEqualTo(key);
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE)).isEqualTo(recognizer);
+    }
+
+    @Test
+    public void setCurrentDefault_typeVoiceRecognitionInfo_setsRecognitionService() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        resolveInfo.serviceInfo.name = TEST_RECOGNIZER;
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_RECOGNITION_SERVICE_TAG, resolveInfo);
+        setupController();
+
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_RECOGNIZER).flattenToString();
+        mController.setCurrentDefault(key);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE)).isEmpty();
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE)).isEqualTo(key);
+    }
+
+    private void setupController() {
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DefaultVoiceInputPickerPreferenceController.class,
+                new LogicalPreferenceGroup(mContext));
+        mController = mControllerHelper.getController();
+    }
+
+    private void setCurrentVoiceInteractionService(ComponentName service) {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, service.flattenToString());
+    }
+
+    private void setCurrentAssistant(ComponentName assist) {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                assist.flattenToString(), TEST_USER_ID);
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/assist/ScreenshotContextPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/assist/ScreenshotContextPreferenceControllerTest.java
new file mode 100644
index 0000000..efd0934
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/assist/ScreenshotContextPreferenceControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ScreenshotContextPreferenceControllerTest {
+
+    private static final int TEST_USER_ID = 10;
+    private static final String TEST_PACKAGE_NAME = "com.test.package";
+    private static final String TEST_SERVICE = "TestService";
+
+    private Context mContext;
+    private TwoStatePreference mTwoStatePreference;
+    private PreferenceControllerTestHelper<ScreenshotContextPreferenceController>
+            mControllerHelper;
+    private ScreenshotContextPreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER_ID);
+
+        mContext = RuntimeEnvironment.application;
+        mTwoStatePreference = new SwitchPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                ScreenshotContextPreferenceController.class, mTwoStatePreference);
+        mController = mControllerHelper.getController();
+
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                key, TEST_USER_ID);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void refreshUi_screenshotEnabled_preferenceChecked() {
+        mTwoStatePreference.setChecked(false);
+
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1);
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_screenshotDisabled_preferenceUnchecked() {
+        mTwoStatePreference.setChecked(true);
+
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 0);
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_structureEnabled_preferenceEnabled() {
+        mTwoStatePreference.setEnabled(false);
+
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1);
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_structureDisabled_preferenceDisabled() {
+        mTwoStatePreference.setEnabled(true);
+
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 0);
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void callChangeListener_toggleTrue_screenshotEnabled() {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 0);
+        mTwoStatePreference.callChangeListener(true);
+
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 0)).isEqualTo(1);
+    }
+
+    @Test
+    public void callChangeListener_toggleFalse_screenshotDisabled() {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1);
+        mTwoStatePreference.callChangeListener(false);
+
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1)).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/assist/TextContextPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/assist/TextContextPreferenceControllerTest.java
new file mode 100644
index 0000000..7b915ea
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/assist/TextContextPreferenceControllerTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class TextContextPreferenceControllerTest {
+
+    private static final int TEST_USER_ID = 10;
+    private static final String TEST_PACKAGE_NAME = "com.test.package";
+    private static final String TEST_SERVICE = "TestService";
+
+    private Context mContext;
+    private TwoStatePreference mTwoStatePreference;
+    private PreferenceControllerTestHelper<TextContextPreferenceController>
+            mControllerHelper;
+    private TextContextPreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER_ID);
+
+        mContext = RuntimeEnvironment.application;
+        mTwoStatePreference = new SwitchPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TextContextPreferenceController.class, mTwoStatePreference);
+        mController = mControllerHelper.getController();
+
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                key, TEST_USER_ID);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void refreshUi_contextEnabled_preferenceChecked() {
+        mTwoStatePreference.setChecked(false);
+
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1);
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_contextDisabled_preferenceUnchecked() {
+        mTwoStatePreference.setChecked(true);
+
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 0);
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void callChangeListener_toggleTrue_contextEnabled() {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 0);
+        mTwoStatePreference.callChangeListener(true);
+
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 0)).isEqualTo(1);
+    }
+
+    @Test
+    public void callChangeListener_toggleFalse_contextDisabled() {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1);
+        mTwoStatePreference.callChangeListener(false);
+
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1)).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/assist/VoiceInputInfoProviderTest.java b/tests/robotests/src/com/android/car/settings/applications/assist/VoiceInputInfoProviderTest.java
new file mode 100644
index 0000000..34361e7
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/assist/VoiceInputInfoProviderTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowVoiceInteractionServiceInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowApplicationPackageManager;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class, ShadowVoiceInteractionServiceInfo.class})
+public class VoiceInputInfoProviderTest {
+
+    private static final String TEST_PACKAGE = "test.package";
+    private static final String TEST_CLASS = "Class1";
+    private static final String TEST_RECOGNITION_SERVICE = "Recognition1";
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @After
+    public void tearDown() {
+        ShadowVoiceInteractionServiceInfo.reset();
+    }
+
+    @Test
+    public void getInteractionInfoList_hasElement() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE;
+        resolveInfo.serviceInfo.name = TEST_CLASS;
+        ShadowVoiceInteractionServiceInfo.setRecognitionService(resolveInfo.serviceInfo,
+                TEST_RECOGNITION_SERVICE);
+
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_INTERACTION_SERVICE_TAG, resolveInfo);
+
+        VoiceInputInfoProvider provider = new VoiceInputInfoProvider(mContext);
+        assertThat(provider.getVoiceInteractionInfoList()).hasSize(1);
+    }
+
+    @Test
+    public void getRecognitionInfoList_hasElement() {
+        ResolveInfo otherInfo = new ResolveInfo();
+        otherInfo.serviceInfo = new ServiceInfo();
+        otherInfo.serviceInfo.packageName = TEST_PACKAGE;
+        otherInfo.serviceInfo.name = TEST_RECOGNITION_SERVICE;
+
+        getShadowPackageManager().addResolveInfoForIntent(
+                VoiceInputInfoProvider.VOICE_RECOGNITION_SERVICE_TAG, otherInfo);
+
+        VoiceInputInfoProvider provider = new VoiceInputInfoProvider(mContext);
+        assertThat(provider.getVoiceRecognitionInfoList()).hasSize(1);
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/assist/VoiceInputUtilsTest.java b/tests/robotests/src/com/android/car/settings/applications/assist/VoiceInputUtilsTest.java
new file mode 100644
index 0000000..7b2b47a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/assist/VoiceInputUtilsTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.assist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class})
+public class VoiceInputUtilsTest {
+
+    private static final String TEST_PACKAGE = "test.package";
+    private static final String TEST_CLASS_1 = "Class1";
+    private static final String TEST_CLASS_2 = "Class2";
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSecureSettings.reset();
+    }
+
+    @Test
+    public void getCurrentService_nullInteraction_nullRecognition_returnsNull() {
+        assertThat(VoiceInputUtils.getCurrentService(mContext)).isNull();
+    }
+
+    @Test
+    public void getCurrentService_emptyInteraction_emptyRecognition_returnsNull() {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, "");
+        assertThat(VoiceInputUtils.getCurrentService(mContext)).isNull();
+    }
+
+    @Test
+    public void getCurrentService_hasInteraction_returnsInteraction() {
+        ComponentName interaction = new ComponentName(TEST_PACKAGE, TEST_CLASS_1);
+        ComponentName recognition = new ComponentName(TEST_PACKAGE, TEST_CLASS_2);
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, interaction.flattenToString());
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, recognition.flattenToString());
+        assertThat(VoiceInputUtils.getCurrentService(mContext)).isEqualTo(interaction);
+    }
+
+    @Test
+    public void getCurrentService_noInteraction_hasRecognition_returnsRecogntion() {
+        ComponentName recognition = new ComponentName(TEST_PACKAGE, TEST_CLASS_2);
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, recognition.flattenToString());
+        assertThat(VoiceInputUtils.getCurrentService(mContext)).isEqualTo(recognition);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppEntryBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppEntryBasePreferenceControllerTest.java
new file mode 100644
index 0000000..955f2d0
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppEntryBasePreferenceControllerTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.applications.DefaultAppInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DefaultAppEntryBasePreferenceControllerTest {
+    private static final CharSequence TEST_LABEL = "Test Label";
+
+    private static class TestDefaultAppEntryBasePreferenceController extends
+            DefaultAppEntryBasePreferenceController<Preference> {
+
+        private DefaultAppInfo mDefaultAppInfo;
+
+        TestDefaultAppEntryBasePreferenceController(Context context,
+                String preferenceKey, FragmentController fragmentController,
+                CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected Class<Preference> getPreferenceType() {
+            return Preference.class;
+        }
+
+        @Nullable
+        @Override
+        protected DefaultAppInfo getCurrentDefaultAppInfo() {
+            return mDefaultAppInfo;
+        }
+
+        protected void setCurrentDefaultAppInfo(DefaultAppInfo defaultAppInfo) {
+            mDefaultAppInfo = defaultAppInfo;
+        }
+    }
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<TestDefaultAppEntryBasePreferenceController>
+            mControllerHelper;
+    private TestDefaultAppEntryBasePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestDefaultAppEntryBasePreferenceController.class,
+                mPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @Test
+    public void refreshUi_hasDefaultAppWithLabel_summaryAndIconAreSet() {
+        DefaultAppInfo defaultAppInfo = mock(DefaultAppInfo.class);
+        when(defaultAppInfo.loadLabel()).thenReturn(TEST_LABEL);
+        when(defaultAppInfo.loadIcon()).thenReturn(mContext.getDrawable(R.drawable.test_icon));
+        mController.setCurrentDefaultAppInfo(defaultAppInfo);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(TEST_LABEL);
+        assertThat(mPreference.getIcon()).isNotNull();
+    }
+
+    @Test
+    public void refreshUi_hasDefaultAppWithoutLabel_summaryAndIconAreNotSet() {
+        DefaultAppInfo defaultAppInfo = mock(DefaultAppInfo.class);
+        when(defaultAppInfo.loadLabel()).thenReturn(null);
+        when(defaultAppInfo.loadIcon()).thenReturn(null);
+        mController.setCurrentDefaultAppInfo(defaultAppInfo);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.app_list_preference_none));
+        assertThat(mPreference.getIcon()).isNull();
+    }
+
+    @Test
+    public void refreshUi_hasNoDefaultApp_summaryAndIconAreNotSet() {
+        mController.setCurrentDefaultAppInfo(null);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.app_list_preference_none));
+        assertThat(mPreference.getIcon()).isNull();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppUtilsTest.java b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppUtilsTest.java
new file mode 100644
index 0000000..a28af16
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppUtilsTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DefaultAppUtilsTest {
+
+    @Test
+    public void setSafeIcon_smallerThanLimit() {
+        Context context = RuntimeEnvironment.application;
+        Drawable drawable = context.getDrawable(R.drawable.test_icon);
+        int height = drawable.getMinimumHeight();
+        int width = drawable.getMinimumWidth();
+
+        // Set to some value larger than current height or width;
+        int testMaxDimensions = Math.max(height, width) + 1;
+        Preference preference = new Preference(context);
+        DefaultAppUtils.setSafeIcon(preference, drawable, testMaxDimensions);
+
+        assertThat(preference.getIcon().getMinimumHeight()).isEqualTo(height);
+        assertThat(preference.getIcon().getMinimumWidth()).isEqualTo(width);
+    }
+
+    @Test
+    public void setSafeIcon_largerThanLimit() {
+        Context context = RuntimeEnvironment.application;
+        Drawable drawable = context.getDrawable(R.drawable.test_icon);
+        int height = drawable.getMinimumHeight();
+        int width = drawable.getMinimumWidth();
+
+        // Set to some value smaller than current height or width;
+        int testMaxDimensions = Math.min(height, width) - 1;
+        Preference preference = new Preference(context);
+        DefaultAppUtils.setSafeIcon(preference, drawable, testMaxDimensions);
+
+        assertThat(preference.getIcon().getMinimumHeight()).isEqualTo(testMaxDimensions);
+        assertThat(preference.getIcon().getMinimumWidth()).isEqualTo(testMaxDimensions);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerBasePreferenceControllerTest.java
new file mode 100644
index 0000000..aeea7bb
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerBasePreferenceControllerTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.applications.DefaultAppInfo;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DefaultAppsPickerBasePreferenceControllerTest {
+
+    private static class TestDefaultAppsPickerBasePreferenceController extends
+            DefaultAppsPickerBasePreferenceController {
+
+        private List<DefaultAppInfo> mCandidates;
+        private String mKey;
+        private CharSequence mMessage;
+        private boolean mIncludeNone = true;
+
+        TestDefaultAppsPickerBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        public void setTestCandidates(List<DefaultAppInfo> candidates) {
+            mCandidates = candidates;
+        }
+
+        public void setTestMessage(String s) {
+            mMessage = s;
+        }
+
+        public void setIncludeNonePreference(boolean include) {
+            mIncludeNone = include;
+        }
+
+        @Override
+        protected List<DefaultAppInfo> getCandidates() {
+            return mCandidates;
+        }
+
+        @Override
+        protected String getCurrentDefaultKey() {
+            return mKey;
+        }
+
+        @Override
+        protected void setCurrentDefault(String key) {
+            mKey = key;
+        }
+
+        @Override
+        protected CharSequence getConfirmationMessage(DefaultAppInfo info) {
+            return mMessage;
+        }
+
+        @Override
+        protected boolean includeNonePreference() {
+            return mIncludeNone;
+        }
+    }
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<TestDefaultAppsPickerBasePreferenceController>
+            mControllerHelper;
+    private TestDefaultAppsPickerBasePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestDefaultAppsPickerBasePreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+    }
+
+    @Test
+    public void refreshUi_noCandidates_hasSingleNoneElement() {
+        mController.setCurrentDefault("");
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        // Has the "None" element.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        Preference preference = mPreferenceGroup.getPreference(0);
+        assertThat(preference.getTitle()).isEqualTo(
+                mContext.getString(R.string.app_list_preference_none));
+        assertThat(preference.getIcon()).isNotNull();
+        assertThat(preference.getSummary()).isEqualTo(
+                mContext.getString(R.string.default_app_selected_app));
+    }
+
+    @Test
+    public void refreshUi_noCandidates_noNoneElement() {
+        mController.setCurrentDefault("");
+        mController.setIncludeNonePreference(false);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        // None element removed.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_hasAdditionalCandidate_hasTwoElements() {
+        String testKey = "testKey";
+
+        DefaultAppInfo testApp = mock(DefaultAppInfo.class);
+        when(testApp.getKey()).thenReturn(testKey);
+        mController.setTestCandidates(Lists.newArrayList(testApp));
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void refreshUi_hasAdditionalCandidateAsDefault_secondElementIsSelected() {
+        String testKey = "testKey";
+
+        DefaultAppInfo testApp = mock(DefaultAppInfo.class);
+        when(testApp.getKey()).thenReturn(testKey);
+        mController.setTestCandidates(Lists.newArrayList(testApp));
+        mController.setCurrentDefault(testKey);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.findPreference(testKey).getSummary()).isEqualTo(
+                mContext.getString(R.string.default_app_selected_app));
+    }
+
+    @Test
+    public void performClick_currentDefaultApp_nothingHappened() {
+        String testKey = "testKey";
+
+        DefaultAppInfo testApp = mock(DefaultAppInfo.class);
+        when(testApp.getKey()).thenReturn(testKey);
+        mController.setTestCandidates(Lists.newArrayList(testApp));
+        mController.setCurrentDefault(testKey);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        Preference currentDefault = mPreferenceGroup.findPreference(testKey);
+        Preference otherOption = mPreferenceGroup.getPreference(0);
+
+        assertThat(TextUtils.isEmpty(currentDefault.getSummary())).isFalse();
+        assertThat(TextUtils.isEmpty(otherOption.getSummary())).isTrue();
+
+        currentDefault.performClick();
+
+        assertThat(TextUtils.isEmpty(currentDefault.getSummary())).isFalse();
+        assertThat(TextUtils.isEmpty(otherOption.getSummary())).isTrue();
+    }
+
+    @Test
+    public void performClick_otherOptionNoMessage_otherOptionSelected() {
+        String testKey = "testKey";
+
+        DefaultAppInfo testApp = mock(DefaultAppInfo.class);
+        when(testApp.getKey()).thenReturn(testKey);
+        mController.setTestCandidates(Lists.newArrayList(testApp));
+        mController.setCurrentDefault(testKey);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        Preference currentDefault = mPreferenceGroup.findPreference(testKey);
+        Preference otherOption = mPreferenceGroup.getPreference(0);
+
+        assertThat(TextUtils.isEmpty(currentDefault.getSummary())).isFalse();
+        assertThat(TextUtils.isEmpty(otherOption.getSummary())).isTrue();
+
+        otherOption.performClick();
+
+        assertThat(TextUtils.isEmpty(currentDefault.getSummary())).isTrue();
+        assertThat(TextUtils.isEmpty(otherOption.getSummary())).isFalse();
+    }
+
+    @Test
+    public void performClick_otherOptionHasMessage_dialogOpened() {
+        String testKey = "testKey";
+
+        DefaultAppInfo testApp = mock(DefaultAppInfo.class);
+        when(testApp.getKey()).thenReturn(testKey);
+        mController.setTestCandidates(Lists.newArrayList(testApp));
+        mController.setCurrentDefault(testKey);
+        mController.setTestMessage("Non-empty message");
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        Preference currentDefault = mPreferenceGroup.findPreference(testKey);
+        Preference otherOption = mPreferenceGroup.getPreference(0);
+
+        assertThat(TextUtils.isEmpty(currentDefault.getSummary())).isFalse();
+        assertThat(TextUtils.isEmpty(otherOption.getSummary())).isTrue();
+
+        otherOption.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmationDialogFragment.class),
+                eq(ConfirmationDialogFragment.TAG));
+    }
+
+    @Test
+    public void performClick_otherOptionNoMessage_newKeySet() {
+        String testKey = "testKey";
+
+        DefaultAppInfo testApp = mock(DefaultAppInfo.class);
+        when(testApp.getKey()).thenReturn(testKey);
+        mController.setTestCandidates(Lists.newArrayList(testApp));
+
+        // Currently, the testApp is the default selection.
+        mController.setCurrentDefault(testKey);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        // This preference represents the "None" option.
+        Preference otherOption = mPreferenceGroup.getPreference(0);
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(testKey);
+
+        otherOption.performClick();
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo("");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerEntryBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerEntryBasePreferenceControllerTest.java
new file mode 100644
index 0000000..8119755
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAppsPickerEntryBasePreferenceControllerTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.applications.DefaultAppInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DefaultAppsPickerEntryBasePreferenceControllerTest {
+
+    private static final Intent TEST_INTENT = new Intent(Settings.ACTION_SETTINGS);
+
+    private static class TestDefaultAppsPickerEntryBasePreferenceController extends
+            DefaultAppsPickerEntryBasePreferenceController {
+
+        private final DefaultAppInfo mDefaultAppInfo;
+        private Intent mSettingIntent;
+
+        TestDefaultAppsPickerEntryBasePreferenceController(Context context,
+                String preferenceKey, FragmentController fragmentController,
+                CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+            mDefaultAppInfo = mock(DefaultAppInfo.class);
+        }
+
+        @Nullable
+        @Override
+        protected Intent getSettingIntent(@Nullable DefaultAppInfo info) {
+            return mSettingIntent;
+        }
+
+        protected void setSettingIntent(Intent settingIntent) {
+            mSettingIntent = settingIntent;
+        }
+
+        @Nullable
+        @Override
+        protected DefaultAppInfo getCurrentDefaultAppInfo() {
+            return mDefaultAppInfo;
+        }
+    }
+
+    private Context mContext;
+    private ButtonPreference mButtonPreference;
+    private PreferenceControllerTestHelper<TestDefaultAppsPickerEntryBasePreferenceController>
+            mControllerHelper;
+    private TestDefaultAppsPickerEntryBasePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mButtonPreference = new ButtonPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestDefaultAppsPickerEntryBasePreferenceController.class, mButtonPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @Test
+    public void refreshUi_hasSettingIntent_actionButtonIsVisible() {
+        mController.setSettingIntent(TEST_INTENT);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mButtonPreference.isActionShown()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_hasNoSettingIntent_actionButtonIsNotVisible() {
+        mController.setSettingIntent(null);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mButtonPreference.isActionShown()).isFalse();
+    }
+
+    @Test
+    public void performButtonClick_launchesIntent() {
+        mController.setSettingIntent(TEST_INTENT);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mButtonPreference.performButtonClick();
+
+        Intent actual = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(actual.getAction()).isEqualTo(TEST_INTENT.getAction());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..a452254
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerEntryPreferenceControllerTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.provider.Settings;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+import com.android.car.settings.testutils.ShadowVoiceInteractionServiceInfo;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowPackageManager;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class, ShadowVoiceInteractionServiceInfo.class,
+        ShadowCarUserManagerHelper.class})
+public class DefaultAssistantPickerEntryPreferenceControllerTest {
+
+    private static final String TEST_PACKAGE = "com.android.car.settings.testutils";
+    private static final String TEST_CLASS = "BaseTestActivity";
+    private static final String TEST_SETTINGS_CLASS = "TestSettingsActivity";
+    private static final String TEST_COMPONENT =
+            new ComponentName(TEST_PACKAGE, TEST_CLASS).flattenToString();
+    private static final int TEST_USER_ID = 10;
+
+    private Context mContext;
+    private ButtonPreference mButtonPreference;
+    private DefaultAssistantPickerEntryPreferenceController mController;
+    private PreferenceControllerTestHelper<DefaultAssistantPickerEntryPreferenceController>
+            mControllerHelper;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+
+        mContext = RuntimeEnvironment.application;
+        mButtonPreference = new ButtonPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DefaultAssistantPickerEntryPreferenceController.class, mButtonPreference);
+        mController = mControllerHelper.getController();
+
+        // Set user.
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER_ID);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowSecureSettings.reset();
+        ShadowVoiceInteractionServiceInfo.reset();
+    }
+
+    @Test
+    public void getCurrentDefaultAppInfo_noAssistant_returnsNull() {
+        assertThat(mController.getCurrentDefaultAppInfo()).isNull();
+    }
+
+    @Test
+    public void getCurrentDefaultAppInfo_hasService_returnsDefaultAppInfo() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ASSISTANT, TEST_COMPONENT, TEST_USER_ID);
+
+        DefaultAppInfo info = mController.getCurrentDefaultAppInfo();
+        assertThat(info.getKey()).isEqualTo(TEST_COMPONENT);
+    }
+
+    @Test
+    public void getSettingIntent_noAssistant_returnsNull() {
+        DefaultAppInfo info = new DefaultAppInfo(mContext,
+                new PackageManagerWrapper(mContext.getPackageManager()), TEST_USER_ID,
+                ComponentName.unflattenFromString(TEST_COMPONENT));
+        assertThat(mController.getSettingIntent(info)).isNull();
+    }
+
+    @Test
+    public void getSettingIntent_hasAssistant_noAssistSupport_returnsNull() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ASSISTANT, TEST_COMPONENT, TEST_USER_ID);
+
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE;
+        resolveInfo.serviceInfo.name = TEST_CLASS;
+
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(resolveInfo.serviceInfo, false);
+        ShadowVoiceInteractionServiceInfo.setSettingsActivity(resolveInfo.serviceInfo,
+                TEST_SETTINGS_CLASS);
+
+        Intent intent =
+                DefaultAssistantPickerEntryPreferenceController.ASSISTANT_SERVICE.setComponent(
+                        ComponentName.unflattenFromString(TEST_COMPONENT));
+        ShadowPackageManager shadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        shadowPackageManager.addResolveInfoForIntent(intent, resolveInfo);
+
+        DefaultAppInfo info = new DefaultAppInfo(mContext,
+                new PackageManagerWrapper(mContext.getPackageManager()), TEST_USER_ID,
+                ComponentName.unflattenFromString(TEST_COMPONENT));
+
+        assertThat(mController.getSettingIntent(info)).isNull();
+    }
+
+    @Test
+    public void getSettingIntent_hasAssistant_supportsAssist_noSettingsActivity_returnsNull() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ASSISTANT, TEST_COMPONENT, TEST_USER_ID);
+
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE;
+        resolveInfo.serviceInfo.name = TEST_CLASS;
+
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(resolveInfo.serviceInfo, true);
+        ShadowVoiceInteractionServiceInfo.setSettingsActivity(resolveInfo.serviceInfo, null);
+
+        Intent intent =
+                DefaultAssistantPickerEntryPreferenceController.ASSISTANT_SERVICE.setComponent(
+                        ComponentName.unflattenFromString(TEST_COMPONENT));
+        ShadowPackageManager shadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        shadowPackageManager.addResolveInfoForIntent(intent, resolveInfo);
+
+        DefaultAppInfo info = new DefaultAppInfo(mContext,
+                new PackageManagerWrapper(mContext.getPackageManager()), TEST_USER_ID,
+                ComponentName.unflattenFromString(TEST_COMPONENT));
+
+        assertThat(mController.getSettingIntent(info)).isNull();
+    }
+
+    @Test
+    public void getSettingIntent_hasAssistant_supportsAssist_hasSettingsActivity_returnsIntent() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ASSISTANT, TEST_COMPONENT, TEST_USER_ID);
+
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE;
+        resolveInfo.serviceInfo.name = TEST_CLASS;
+
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(resolveInfo.serviceInfo, true);
+        ShadowVoiceInteractionServiceInfo.setSettingsActivity(resolveInfo.serviceInfo,
+                TEST_SETTINGS_CLASS);
+
+        Intent intent =
+                DefaultAssistantPickerEntryPreferenceController.ASSISTANT_SERVICE.setComponent(
+                        ComponentName.unflattenFromString(TEST_COMPONENT));
+        ShadowPackageManager shadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        shadowPackageManager.addResolveInfoForIntent(intent, resolveInfo);
+
+        DefaultAppInfo info = new DefaultAppInfo(mContext,
+                new PackageManagerWrapper(mContext.getPackageManager()), TEST_USER_ID,
+                ComponentName.unflattenFromString(TEST_COMPONENT));
+
+        Intent result = mController.getSettingIntent(info);
+        assertThat(result.getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(result.getComponent()).isEqualTo(
+                new ComponentName(TEST_PACKAGE, TEST_SETTINGS_CLASS));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerPreferenceControllerTest.java
new file mode 100644
index 0000000..308f8d4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAssistantPickerPreferenceControllerTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+import com.android.car.settings.testutils.ShadowVoiceInteractionServiceInfo;
+
+import com.google.android.collect.Lists;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowPackageManager;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class, ShadowVoiceInteractionServiceInfo.class,
+        ShadowCarUserManagerHelper.class})
+public class DefaultAssistantPickerPreferenceControllerTest {
+
+    private static final String TEST_PACKAGE_NAME = "com.test.package";
+    private static final String TEST_SERVICE = "TestService";
+    private static final String TEST_ACTIVITY = "TestActivity";
+    private static final int TEST_USER_ID = 10;
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<DefaultAssistantPickerPreferenceController>
+            mControllerHelper;
+    private DefaultAssistantPickerPreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DefaultAssistantPickerPreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+
+        // Set user.
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER_ID);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSecureSettings.reset();
+        ShadowCarUserManagerHelper.reset();
+        ShadowVoiceInteractionServiceInfo.reset();
+    }
+
+    @Test
+    public void getCandidates_oneActivityOneService_returnsTwoElements() {
+        // Included. Supports assist.
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(serviceResolveInfo.serviceInfo, true);
+
+        // Included. Different package.
+        ResolveInfo activityResolveInfoDifferentPackage = new ResolveInfo();
+        activityResolveInfoDifferentPackage.activityInfo = new ActivityInfo();
+        activityResolveInfoDifferentPackage.activityInfo.packageName = "com.other.package";
+        activityResolveInfoDifferentPackage.activityInfo.name = TEST_ACTIVITY;
+
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_SERVICE_PROBE,
+                Lists.newArrayList(serviceResolveInfo));
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_ACTIVITY_PROBE,
+                Lists.newArrayList(activityResolveInfoDifferentPackage));
+
+        // One service, one activity.
+        assertThat(mController.getCandidates()).hasSize(2);
+    }
+
+    @Test
+    public void getCandidates_filtersServicesWithoutAssist_returnsOneElement() {
+        // Included. Supports assist.
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(serviceResolveInfo.serviceInfo, true);
+
+        // Not included. Doesn't support assist.
+        ResolveInfo serviceResolveInfoNoAssist = new ResolveInfo();
+        serviceResolveInfoNoAssist.serviceInfo = new ServiceInfo();
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(serviceResolveInfoNoAssist.serviceInfo,
+                false);
+
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_SERVICE_PROBE,
+                Lists.newArrayList(serviceResolveInfo, serviceResolveInfoNoAssist));
+
+        // Single service supporting assist.
+        assertThat(mController.getCandidates()).hasSize(1);
+    }
+
+    @Test
+    public void getCandidates_filtersSamePackageName_returnsOneElement() {
+        // Included. Supports assist.
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(serviceResolveInfo.serviceInfo, true);
+
+        // Not included. Same package as service above.
+        ResolveInfo activityResolveInfo = new ResolveInfo();
+        activityResolveInfo.activityInfo = new ActivityInfo();
+        activityResolveInfo.activityInfo.packageName = TEST_PACKAGE_NAME;
+        activityResolveInfo.activityInfo.name = TEST_ACTIVITY;
+
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_SERVICE_PROBE,
+                Lists.newArrayList(serviceResolveInfo));
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_ACTIVITY_PROBE,
+                Lists.newArrayList(activityResolveInfo));
+
+        // Single service due to package name.
+        assertThat(mController.getCandidates()).hasSize(1);
+    }
+
+    @Test
+    public void getCurrentDefaultKey_noneSet_returnsNonePreferenceKey() {
+        // Since Settings.Secure.ASSISTANT is not yet set, it should be null in SecureSettings.
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(
+                DefaultAppsPickerBasePreferenceController.NONE_PREFERENCE_KEY);
+    }
+
+    @Test
+    public void getCurrentDefaultKey_testAssistantSet_returnsAssistantPreferenceKey() {
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_ACTIVITY).flattenToString();
+
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT,
+                key, TEST_USER_ID);
+
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(key);
+    }
+
+    @Test
+    public void setCurrentDefault_nullKey_setsNonePreference() {
+        mController.setCurrentDefault(null);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ASSISTANT)).isEmpty();
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE)).isEmpty();
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE)).isEmpty();
+    }
+
+    @Test
+    public void setCurrentDefault_keyNotInCandidates_setsNonePreference() {
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(serviceResolveInfo.serviceInfo, true);
+
+        ResolveInfo activityResolveInfoDifferentPackage = new ResolveInfo();
+        activityResolveInfoDifferentPackage.activityInfo = new ActivityInfo();
+        activityResolveInfoDifferentPackage.activityInfo.packageName = "com.other.package";
+        activityResolveInfoDifferentPackage.activityInfo.name = TEST_ACTIVITY;
+
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_SERVICE_PROBE,
+                Lists.newArrayList(serviceResolveInfo));
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_ACTIVITY_PROBE,
+                Lists.newArrayList(activityResolveInfoDifferentPackage));
+        mController.getCandidates();
+
+        String testKey = new ComponentName("com.not.existent.key", "TestApp").flattenToString();
+        mController.setCurrentDefault(testKey);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ASSISTANT)).isEmpty();
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE)).isEmpty();
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE)).isEmpty();
+    }
+
+    @Test
+    public void setCurrentDefaultKey_keySelectsService_setsService() {
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(serviceResolveInfo.serviceInfo, true);
+        ShadowVoiceInteractionServiceInfo.setRecognitionService(serviceResolveInfo.serviceInfo,
+                "TestRecognitionService");
+
+        ResolveInfo activityResolveInfoDifferentPackage = new ResolveInfo();
+        activityResolveInfoDifferentPackage.activityInfo = new ActivityInfo();
+        activityResolveInfoDifferentPackage.activityInfo.packageName = "com.other.package";
+        activityResolveInfoDifferentPackage.activityInfo.name = TEST_ACTIVITY;
+
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_SERVICE_PROBE,
+                Lists.newArrayList(serviceResolveInfo));
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_ACTIVITY_PROBE,
+                Lists.newArrayList(activityResolveInfoDifferentPackage));
+        mController.getCandidates();
+
+        String testKey = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        mController.setCurrentDefault(testKey);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ASSISTANT)).isEqualTo(testKey);
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE)).isEqualTo(testKey);
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE)).isEqualTo(
+                new ComponentName(TEST_PACKAGE_NAME, "TestRecognitionService").flattenToString());
+    }
+
+    @Test
+    public void setCurrentDefaultKey_keySelectsActivity_setsActivity() {
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        ShadowVoiceInteractionServiceInfo.setSupportsAssist(serviceResolveInfo.serviceInfo, true);
+
+        ResolveInfo activityResolveInfoDifferentPackage = new ResolveInfo();
+        activityResolveInfoDifferentPackage.activityInfo = new ActivityInfo();
+        activityResolveInfoDifferentPackage.activityInfo.packageName = "com.other.package";
+        activityResolveInfoDifferentPackage.activityInfo.name = TEST_ACTIVITY;
+
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_SERVICE_PROBE,
+                Lists.newArrayList(serviceResolveInfo));
+        getShadowApplicationManager().addResolveInfoForIntent(
+                DefaultAssistantPickerPreferenceController.ASSIST_ACTIVITY_PROBE,
+                Lists.newArrayList(activityResolveInfoDifferentPackage));
+        mController.getCandidates();
+
+        String testKey = new ComponentName("com.other.package", TEST_ACTIVITY).flattenToString();
+
+        mController.setCurrentDefault(testKey);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ASSISTANT)).isEqualTo(testKey);
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE)).isEmpty();
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE)).isEmpty();
+    }
+
+    private ShadowPackageManager getShadowApplicationManager() {
+        return Shadows.shadowOf(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..71f4362
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerEntryPreferenceControllerTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.provider.Settings;
+import android.service.autofill.AutofillService;
+import android.view.autofill.AutofillManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAutofillServiceInfo;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.wrapper.PackageManagerWrapper;
+
+import com.google.android.collect.Lists;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowPackageManager;
+
+import java.util.Collections;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class, ShadowAutofillServiceInfo.class})
+public class DefaultAutofillPickerEntryPreferenceControllerTest {
+
+    private static final String TEST_PACKAGE = "com.android.car.settings.testutils";
+    private static final String TEST_CLASS = "BaseTestActivity";
+    private static final String TEST_OTHER_CLASS = "BaseTestOtherActivity";
+    private static final String TEST_COMPONENT =
+            new ComponentName(TEST_PACKAGE, TEST_CLASS).flattenToString();
+    private static final int TEST_USER_ID = 10;
+
+    private Context mContext;
+    private ButtonPreference mButtonPreference;
+    private DefaultAutofillPickerEntryPreferenceController mController;
+    private PreferenceControllerTestHelper<DefaultAutofillPickerEntryPreferenceController>
+            mControllerHelper;
+    @Mock
+    private AutofillManager mAutofillManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Shadows.shadowOf(RuntimeEnvironment.application).setSystemService(
+                Context.AUTOFILL_MANAGER_SERVICE, mAutofillManager);
+        mContext = RuntimeEnvironment.application;
+        mButtonPreference = new ButtonPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DefaultAutofillPickerEntryPreferenceController.class, mButtonPreference);
+        mController = mControllerHelper.getController();
+
+        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE,
+                "");
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSecureSettings.reset();
+        ShadowAutofillServiceInfo.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_autofillManagerIsNull_unsupportedOnDevice() {
+        Shadows.shadowOf(RuntimeEnvironment.application).setSystemService(
+                Context.AUTOFILL_MANAGER_SERVICE, null);
+
+        // Reinitialize so that it uses the system service set in this test.
+        ButtonPreference preference = new ButtonPreference(mContext);
+        PreferenceControllerTestHelper<DefaultAutofillPickerEntryPreferenceController> helper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        DefaultAutofillPickerEntryPreferenceController.class, preference);
+        DefaultAutofillPickerEntryPreferenceController controller = helper.getController();
+
+        assertThat(controller.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_autofillNotSupported_unsupportedOnDevice() {
+        when(mAutofillManager.isAutofillSupported()).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_autofillSupported_isAvailable() {
+        when(mAutofillManager.isAutofillSupported()).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getCurrentDefaultAppInfo_noService_returnsNull() {
+        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE,
+                "");
+
+        assertThat(mController.getCurrentDefaultAppInfo()).isNull();
+    }
+
+    @Test
+    public void getCurrentDefaultAppInfo_hasService_returnsDefaultAppInfo() {
+        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE,
+                TEST_COMPONENT);
+
+        DefaultAppInfo info = mController.getCurrentDefaultAppInfo();
+        assertThat(info.getKey()).isEqualTo(TEST_COMPONENT);
+    }
+
+    @Test
+    public void getSettingIntent_nullDefaultAppInfo_returnsNull() {
+        assertThat(mController.getSettingIntent(null)).isNull();
+    }
+
+    @Test
+    public void getSettingIntent_noServiceInterface_returnsNull() {
+        Intent intent = new Intent(AutofillService.SERVICE_INTERFACE);
+        ShadowPackageManager shadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        shadowPackageManager.addResolveInfoForIntent(intent, Collections.emptyList());
+
+        DefaultAppInfo info = new DefaultAppInfo(mContext,
+                new PackageManagerWrapper(mContext.getPackageManager()), TEST_USER_ID,
+                ComponentName.unflattenFromString(TEST_COMPONENT));
+
+        assertThat(mController.getSettingIntent(info)).isNull();
+    }
+
+    @Test
+    public void getSettingIntent_hasServiceInterface_returnsIntent() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = TEST_PACKAGE;
+        resolveInfo.serviceInfo.name = TEST_CLASS;
+
+        ShadowAutofillServiceInfo.setSettingsActivity(TEST_OTHER_CLASS);
+
+        Intent intent = new Intent(AutofillService.SERVICE_INTERFACE);
+        ShadowPackageManager shadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        shadowPackageManager.addResolveInfoForIntent(intent, Lists.newArrayList(resolveInfo));
+
+        DefaultAppInfo info = new DefaultAppInfo(mContext,
+                new PackageManagerWrapper(mContext.getPackageManager()), TEST_USER_ID,
+                ComponentName.unflattenFromString(TEST_COMPONENT));
+
+        Intent result = mController.getSettingIntent(info);
+        assertThat(result.getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(result.getComponent()).isEqualTo(
+                new ComponentName(TEST_PACKAGE, TEST_OTHER_CLASS));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerPreferenceControllerTest.java
new file mode 100644
index 0000000..4a15e47
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/defaultapps/DefaultAutofillPickerPreferenceControllerTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.defaultapps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.provider.Settings;
+import android.service.autofill.AutofillService;
+
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowPackageManager;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class})
+public class DefaultAutofillPickerPreferenceControllerTest {
+
+    private static final String TEST_PACKAGE_NAME = "com.test.package";
+    private static final String TEST_SERVICE = "TestService";
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private DefaultAutofillPickerPreferenceController mController;
+    private PreferenceControllerTestHelper<DefaultAutofillPickerPreferenceController>
+            mControllerHelper;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DefaultAutofillPickerPreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSecureSettings.reset();
+    }
+
+    @Test
+    public void getCandidates_hasServiceWithoutPermissions_returnsEmptyList() {
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        serviceResolveInfo.serviceInfo.permission = "";
+        getShadowPackageManager().addResolveInfoForIntent(
+                new Intent(AutofillService.SERVICE_INTERFACE), serviceResolveInfo);
+
+        assertThat(mController.getCandidates()).hasSize(0);
+    }
+
+    @Test
+    public void getCandidates_hasServiceWithBindAutofillServicePermission_returnsService() {
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        serviceResolveInfo.serviceInfo.permission = Manifest.permission.BIND_AUTOFILL_SERVICE;
+        getShadowPackageManager().addResolveInfoForIntent(
+                new Intent(AutofillService.SERVICE_INTERFACE), serviceResolveInfo);
+
+        assertThat(mController.getCandidates()).hasSize(1);
+    }
+
+    @Test
+    public void getCandidates_hasServiceWithBindAutofillPermission_returnsEmptyList() {
+        ResolveInfo serviceResolveInfo = new ResolveInfo();
+        serviceResolveInfo.serviceInfo = new ServiceInfo();
+        serviceResolveInfo.serviceInfo.packageName = TEST_PACKAGE_NAME;
+        serviceResolveInfo.serviceInfo.name = TEST_SERVICE;
+        serviceResolveInfo.serviceInfo.permission = Manifest.permission.BIND_AUTOFILL;
+        getShadowPackageManager().addResolveInfoForIntent(
+                new Intent(AutofillService.SERVICE_INTERFACE), serviceResolveInfo);
+
+        assertThat(mController.getCandidates()).hasSize(0);
+    }
+
+    @Test
+    public void getCurrentDefaultKey_secureSettingEmpty_returnsNoneKey() {
+        // Secure Setting not set should return null.
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(
+                DefaultAppsPickerBasePreferenceController.NONE_PREFERENCE_KEY);
+    }
+
+    @Test
+    public void getCurrentDefaultKey_secureSettingReturnsInvalidString_returnsNoneKey() {
+        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE,
+                "invalid");
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(
+                DefaultAppsPickerBasePreferenceController.NONE_PREFERENCE_KEY);
+    }
+
+    @Test
+    public void getCurrentDefaultKey_secureSettingReturnsValidString_returnsCorrectKey() {
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE,
+                key);
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(key);
+    }
+
+    @Test
+    public void setCurrentDefault_setInvalidKey_getCurrentDefaultKeyReturnsNone() {
+        mController.setCurrentDefault("invalid");
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(
+                DefaultAppsPickerBasePreferenceController.NONE_PREFERENCE_KEY);
+    }
+
+    @Test
+    public void setCurrentDefault_setValidKey_getCurrentDefaultKeyReturnsKey() {
+        String key = new ComponentName(TEST_PACKAGE_NAME, TEST_SERVICE).flattenToString();
+        mController.setCurrentDefault(key);
+        assertThat(mController.getCurrentDefaultKey()).isEqualTo(key);
+    }
+
+    private ShadowPackageManager getShadowPackageManager() {
+        return Shadows.shadowOf(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/managedomainurls/AppLinkStatePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/AppLinkStatePreferenceControllerTest.java
new file mode 100644
index 0000000..1e9b365
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/AppLinkStatePreferenceControllerTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.applications.ApplicationsState;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowApplicationPackageManager.class})
+public class AppLinkStatePreferenceControllerTest {
+
+    private static final int USER_ID = 10;
+    private static final String TEST_PACKAGE_NAME = "com.example.test";
+    private static final int TEST_PACKAGE_ID = 1;
+    private static final String TEST_LABEL = "Test App";
+    private static final String TEST_PATH = "TEST_PATH";
+    private static final String TEST_ACTIVITY = "TestActivity";
+
+    private Context mContext;
+    private ListPreference mPreference;
+    private PreferenceControllerTestHelper<AppLinkStatePreferenceController> mControllerHelper;
+    private AppLinkStatePreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(USER_ID);
+
+        mContext = RuntimeEnvironment.application;
+        mPreference = new ListPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AppLinkStatePreferenceController.class);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowApplicationPackageManager.reset();
+    }
+
+    @Test
+    public void refreshUi_isBrowserApp_isDisabled() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+
+        setupIsBrowserApp(true);
+
+        mController.setAppEntry(entry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_isNotBrowserApp_noDomainUrls_isDisabled() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        info.privateFlags = 0;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+
+        setupIsBrowserApp(false);
+
+        mController.setAppEntry(entry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_isNotBrowserApp_hasDomainUrls_isEnabled() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        info.privateFlags = ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+
+        setupIsBrowserApp(false);
+
+        mController.setAppEntry(entry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_isNotBrowserApp_hasDomainUrls_defaultState_entrySetToAsk() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        info.privateFlags = ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+
+        setupIsBrowserApp(false);
+
+        mController.setAppEntry(entry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getEntry()).isEqualTo(
+                mContext.getString(R.string.app_link_open_ask));
+    }
+
+    @Test
+    public void refreshUi_isNotBrowserApp_hasDomainUrls_askState_entrySetToAsk() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        info.privateFlags = ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+
+        setupIsBrowserApp(false);
+        mContext.getPackageManager().updateIntentVerificationStatusAsUser(TEST_PACKAGE_NAME,
+                INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK, USER_ID);
+
+        mController.setAppEntry(entry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getEntry()).isEqualTo(
+                mContext.getString(R.string.app_link_open_ask));
+    }
+
+    @Test
+    public void refreshUi_isNotBrowserApp_hasDomainUrls_alwaysState_entrySetToAlways() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        info.privateFlags = ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+
+        setupIsBrowserApp(false);
+        mContext.getPackageManager().updateIntentVerificationStatusAsUser(TEST_PACKAGE_NAME,
+                INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, USER_ID);
+
+        mController.setAppEntry(entry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getEntry()).isEqualTo(
+                mContext.getString(R.string.app_link_open_always));
+    }
+
+    @Test
+    public void refreshUi_isNotBrowserApp_hasDomainUrls_neverState_entrySetToNever() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        info.privateFlags = ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+
+        setupIsBrowserApp(false);
+        mContext.getPackageManager().updateIntentVerificationStatusAsUser(TEST_PACKAGE_NAME,
+                INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, USER_ID);
+
+        mController.setAppEntry(entry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getEntry()).isEqualTo(
+                mContext.getString(R.string.app_link_open_never));
+    }
+
+    private void setupIsBrowserApp(boolean isBrowserApp) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.handleAllWebDataURI = isBrowserApp;
+        AppLaunchSettingsBasePreferenceController.sBrowserIntent.setPackage(TEST_PACKAGE_NAME);
+        getShadowPackageManager().addResolveInfoForIntent(
+                AppLaunchSettingsBasePreferenceController.sBrowserIntent,
+                Arrays.asList(resolveInfo));
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/managedomainurls/ClearDefaultsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/ClearDefaultsPreferenceControllerTest.java
new file mode 100644
index 0000000..b69ad96
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/ClearDefaultsPreferenceControllerTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.hardware.usb.IUsbManager;
+import android.os.RemoteException;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowIUsbManager;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowApplicationPackageManager.class,
+        ShadowIUsbManager.class})
+public class ClearDefaultsPreferenceControllerTest {
+
+    private static final int USER_ID = 10;
+    private static final String TEST_PACKAGE_NAME = "com.example.test";
+    private static final int TEST_PACKAGE_ID = 1;
+    private static final String TEST_LABEL = "Test App";
+    private static final String TEST_PATH = "TEST_PATH";
+    private static final String TEST_ACTIVITY = "TestActivity";
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<ClearDefaultsPreferenceController> mControllerHelper;
+    private ClearDefaultsPreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private IUsbManager mIUsbManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        ShadowIUsbManager.setInstance(mIUsbManager);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(USER_ID);
+
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                ClearDefaultsPreferenceController.class);
+        mController = mControllerHelper.getController();
+
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+
+        mController.setAppEntry(entry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowIUsbManager.reset();
+        ShadowApplicationPackageManager.reset();
+    }
+
+    @Test
+    public void refreshUi_hasPreferredActivities_hasSummary() {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
+        filter.addCategory(Intent.CATEGORY_HOME);
+        ComponentName name = new ComponentName(TEST_PACKAGE_NAME, TEST_ACTIVITY);
+        getShadowPackageManager().addPreferredActivity(filter, 0, null, name);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary().toString()).isNotEmpty();
+    }
+
+    @Test
+    public void refreshUi_isDefaultBrowser_hasSummary() {
+        getShadowPackageManager().setDefaultBrowserPackageNameAsUser(TEST_PACKAGE_NAME, USER_ID);
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary().toString()).isNotEmpty();
+    }
+
+    @Test
+    public void refreshUi_hasUsbDefaults_hasSummary() throws RemoteException {
+        when(mIUsbManager.hasDefaults(TEST_PACKAGE_NAME, USER_ID)).thenReturn(true);
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary().toString()).isNotEmpty();
+    }
+
+    @Test
+    public void refreshUi_autoLaunchDisabled_hasNoSummary() {
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isNull();
+    }
+
+    @Test
+    public void performClick_hasUsbManager_hasPreferredActivities_clearsPreferredActivities() {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
+        filter.addCategory(Intent.CATEGORY_HOME);
+        ComponentName name = new ComponentName(TEST_PACKAGE_NAME, TEST_ACTIVITY);
+        getShadowPackageManager().addPreferredActivity(filter, 0, null, name);
+        assertThat(AppUtils.hasPreferredActivities(mContext.getPackageManager(),
+                TEST_PACKAGE_NAME)).isTrue();
+        mController.refreshUi();
+        mPreference.performClick();
+
+        assertThat(AppUtils.hasPreferredActivities(mContext.getPackageManager(),
+                TEST_PACKAGE_NAME)).isFalse();
+    }
+
+    @Test
+    public void performClick_hasUsbManager_isDefaultBrowser_clearsDefaultBrowser() {
+        getShadowPackageManager().setDefaultBrowserPackageNameAsUser(TEST_PACKAGE_NAME, USER_ID);
+        assertThat(
+                getShadowPackageManager().getDefaultBrowserPackageNameAsUser(USER_ID)).isNotNull();
+        mController.refreshUi();
+        mPreference.performClick();
+
+        assertThat(getShadowPackageManager().getDefaultBrowserPackageNameAsUser(USER_ID)).isNull();
+    }
+
+    @Test
+    public void performClick_hasUsbDefaults_clearsUsbDefaults() throws RemoteException {
+        when(mIUsbManager.hasDefaults(TEST_PACKAGE_NAME, USER_ID)).thenReturn(true);
+        mController.refreshUi();
+        mPreference.performClick();
+
+        verify(mIUsbManager).clearDefaults(TEST_PACKAGE_NAME, USER_ID);
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainAppPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainAppPreferenceControllerTest.java
new file mode 100644
index 0000000..e567cb6
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainAppPreferenceControllerTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import static android.content.pm.UserInfo.FLAG_ADMIN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.UserManager;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowIconDrawableFactory;
+import com.android.car.settings.testutils.ShadowUserManager;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.ArrayList;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowUserManager.class, ShadowCarUserManagerHelper.class,
+        ShadowIconDrawableFactory.class})
+public class DomainAppPreferenceControllerTest {
+
+    private static final int USER_ID = 10;
+    private static final String TEST_PACKAGE_NAME = "com.android.test.package";
+    private static final int TEST_PACKAGE_ID = 1;
+    private static final String TEST_LABEL = "Test App";
+    private static final String TEST_PATH = "TEST_PATH";
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<DomainAppPreferenceController> mControllerHelper;
+    private DomainAppPreferenceController mController;
+    private Lifecycle mLifecycle;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(USER_ID);
+
+        mContext = RuntimeEnvironment.application;
+        getShadowUserManager().addProfile(USER_ID, USER_ID, "Test Name", /* profileFlags= */
+                FLAG_ADMIN);
+
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DomainAppPreferenceController.class);
+        mController = mControllerHelper.getController();
+
+        LifecycleOwner lifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(lifecycleOwner);
+        mController.setLifecycle(mLifecycle);
+
+        mControllerHelper.setPreference(mPreferenceGroup);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowUserManager.reset();
+    }
+
+    @Test
+    public void checkInitialized_noLifecycle_throwsError() {
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DomainAppPreferenceController.class);
+
+        assertThrows(IllegalStateException.class,
+                () -> mControllerHelper.setPreference(mPreferenceGroup));
+    }
+
+    @Test
+    public void onRebuildComplete_sessionLoadsValues_preferenceGroupHasValues() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+        entry.label = TEST_LABEL;
+        apps.add(entry);
+        mController.mApplicationStateCallbacks.onRebuildComplete(apps);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void performClick_startsApplicationLaunchSettingsFragmentWithPackageName() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        ApplicationsState.AppEntry entry = new ApplicationsState.AppEntry(mContext, info,
+                TEST_PACKAGE_ID);
+        entry.label = TEST_LABEL;
+        apps.add(entry);
+        mController.mApplicationStateCallbacks.onRebuildComplete(apps);
+
+        Preference preference = mPreferenceGroup.getPreference(0);
+        preference.performClick();
+
+        ArgumentCaptor<ApplicationLaunchSettingsFragment> captor = ArgumentCaptor.forClass(
+                ApplicationLaunchSettingsFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).launchFragment(captor.capture());
+
+        String pkgName = captor.getValue().getArguments().getString(
+                ApplicationLaunchSettingsFragment.ARG_PACKAGE_NAME);
+        assertThat(pkgName).isEqualTo(TEST_PACKAGE_NAME);
+    }
+
+    private ShadowUserManager getShadowUserManager() {
+        return Shadow.extract(UserManager.get(mContext));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainUrlsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainUrlsPreferenceControllerTest.java
new file mode 100644
index 0000000..70478bc
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainUrlsPreferenceControllerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.applications.ApplicationsState;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class, ShadowCarUserManagerHelper.class})
+public class DomainUrlsPreferenceControllerTest {
+
+    private static final int USER_ID = 10;
+    private static final String TEST_PACKAGE_NAME = "com.example.test";
+    private static final int TEST_PACKAGE_ID = 1;
+    private static final String TEST_LABEL = "Test App";
+    private static final String TEST_PATH = "TEST_PATH";
+    private static final String TEST_ACTIVITY = "TestActivity";
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<DomainUrlsPreferenceController> mControllerHelper;
+    private DomainUrlsPreferenceController mController;
+    private ApplicationsState.AppEntry mAppEntry;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(USER_ID);
+
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DomainUrlsPreferenceController.class);
+        mController = mControllerHelper.getController();
+
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = TEST_PACKAGE_NAME;
+        info.uid = TEST_PACKAGE_ID;
+        info.sourceDir = TEST_PATH;
+        mAppEntry = new ApplicationsState.AppEntry(mContext, info, TEST_PACKAGE_ID);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowApplicationPackageManager.reset();
+    }
+
+    @Test
+    public void refreshUi_isBrowserApp_isDisabled() {
+        setupIsBrowserApp(true);
+
+        mController.setAppEntry(mAppEntry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_isNotBrowserApp_isEnabled() {
+        setupIsBrowserApp(false);
+
+        mController.setAppEntry(mAppEntry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_isBrowserApp_summarySet() {
+        setupIsBrowserApp(true);
+
+        mController.setAppEntry(mAppEntry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isNotNull();
+    }
+
+    @Test
+    public void refreshUi_isNotBrowserApp_summarySet() {
+        setupIsBrowserApp(false);
+
+        mController.setAppEntry(mAppEntry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isNotNull();
+    }
+
+    @Test
+    public void performClick_isNotBrowserApp_opensDialog() {
+        setupIsBrowserApp(false);
+
+        mController.setAppEntry(mAppEntry);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mPreference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmationDialogFragment.class), eq(ConfirmationDialogFragment.TAG));
+    }
+
+    private void setupIsBrowserApp(boolean isBrowserApp) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.handleAllWebDataURI = isBrowserApp;
+        AppLaunchSettingsBasePreferenceController.sBrowserIntent.setPackage(TEST_PACKAGE_NAME);
+        getShadowPackageManager().addResolveInfoForIntent(
+                AppLaunchSettingsBasePreferenceController.sBrowserIntent,
+                Arrays.asList(resolveInfo));
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainUrlsUtilsTest.java b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainUrlsUtilsTest.java
new file mode 100644
index 0000000..fc0cbcf
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/managedomainurls/DomainUrlsUtilsTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.managedomainurls;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
+import android.util.ArraySet;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class})
+public class DomainUrlsUtilsTest {
+
+    private static final String TEST_PACKAGE = "com.test.android.Package";
+    private static final int USER_ID = 10;
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @Test
+    public void getDomainsSummary_domainStatusSetToNever_showNoneText() {
+        mContext.getPackageManager().updateIntentVerificationStatusAsUser(TEST_PACKAGE,
+                PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, USER_ID);
+
+        assertThat(DomainUrlsUtils.getDomainsSummary(mContext, TEST_PACKAGE, USER_ID,
+                new ArraySet<>())).isEqualTo(mContext.getString(R.string.domain_urls_summary_none));
+    }
+
+    @Test
+    public void getDomainsSummary_domainStatusSet_noDomains_showNoneText() {
+        mContext.getPackageManager().updateIntentVerificationStatusAsUser(TEST_PACKAGE,
+                PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK, USER_ID);
+
+        assertThat(DomainUrlsUtils.getDomainsSummary(mContext, TEST_PACKAGE, USER_ID,
+                new ArraySet<>())).isEqualTo(mContext.getString(R.string.domain_urls_summary_none));
+    }
+
+    @Test
+    public void getDomainsSummary_domainStatusSet_oneDomain_showSingleDomain() {
+        mContext.getPackageManager().updateIntentVerificationStatusAsUser(TEST_PACKAGE,
+                PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK, USER_ID);
+        ArraySet<String> domains = new ArraySet<>();
+        domains.add("test.domain.com");
+
+        assertThat(DomainUrlsUtils.getDomainsSummary(mContext, TEST_PACKAGE, USER_ID,
+                domains)).isEqualTo(
+                mContext.getString(R.string.domain_urls_summary_one, domains.valueAt(0)));
+    }
+
+    @Test
+    public void getDomainsSummary_domainStatusSet_multipleDomain_showMultipleDomains() {
+        mContext.getPackageManager().updateIntentVerificationStatusAsUser(TEST_PACKAGE,
+                PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK, USER_ID);
+        ArraySet<String> domains = new ArraySet<>();
+        domains.add("test.domain.com");
+        domains.add("test.domain2.com");
+
+        assertThat(DomainUrlsUtils.getDomainsSummary(mContext, TEST_PACKAGE, USER_ID,
+                domains)).isEqualTo(
+                mContext.getString(R.string.domain_urls_summary_some, domains.valueAt(0)));
+    }
+
+    @Test
+    public void getHandledDomains_includeIntentFilterVerificationInfoDomains() {
+        PackageManager pm = mock(PackageManager.class);
+        String domain = "test.domain.com";
+        ArraySet<String> domains = new ArraySet<>();
+        domains.add(domain);
+        IntentFilterVerificationInfo info = new IntentFilterVerificationInfo(TEST_PACKAGE, domains);
+        when(pm.getIntentFilterVerifications(TEST_PACKAGE)).thenReturn(Arrays.asList(info));
+
+        assertThat(DomainUrlsUtils.getHandledDomains(pm, TEST_PACKAGE)).hasSize(1);
+        assertThat(DomainUrlsUtils.getHandledDomains(pm, TEST_PACKAGE)).contains(domain);
+    }
+
+    @Test
+    public void getHandledDomains_includeBrowsableIntentFiltersWithHttpDataScheme() {
+        PackageManager pm = mock(PackageManager.class);
+        String domain = "test.domain.com";
+        String port = "80";
+        IntentFilter filter = new IntentFilter();
+        filter.addCategory(Intent.CATEGORY_BROWSABLE);
+        filter.addDataScheme(IntentFilter.SCHEME_HTTP);
+        filter.addDataAuthority(new IntentFilter.AuthorityEntry(domain, port));
+        when(pm.getAllIntentFilters(TEST_PACKAGE)).thenReturn(Arrays.asList(filter));
+
+        assertThat(DomainUrlsUtils.getHandledDomains(pm, TEST_PACKAGE)).hasSize(1);
+        assertThat(DomainUrlsUtils.getHandledDomains(pm, TEST_PACKAGE)).contains(domain);
+    }
+
+    @Test
+    public void getHandledDomains_includeBrowsableIntentFiltersWithHttpsDataScheme() {
+        PackageManager pm = mock(PackageManager.class);
+        String domain = "test.domain.com";
+        String port = "80";
+        IntentFilter filter = new IntentFilter();
+        filter.addCategory(Intent.CATEGORY_BROWSABLE);
+        filter.addDataScheme(IntentFilter.SCHEME_HTTPS);
+        filter.addDataAuthority(new IntentFilter.AuthorityEntry(domain, port));
+        when(pm.getAllIntentFilters(TEST_PACKAGE)).thenReturn(Arrays.asList(filter));
+
+        assertThat(DomainUrlsUtils.getHandledDomains(pm, TEST_PACKAGE)).hasSize(1);
+        assertThat(DomainUrlsUtils.getHandledDomains(pm, TEST_PACKAGE)).contains(domain);
+    }
+
+    @Test
+    public void getHandledDomains_excludeBrowsableIntentFiltersWithOtherDataScheme() {
+        PackageManager pm = mock(PackageManager.class);
+        String domain = "test.domain.com";
+        String port = "80";
+        IntentFilter filter = new IntentFilter();
+        filter.addCategory(Intent.CATEGORY_BROWSABLE);
+        filter.addDataAuthority(new IntentFilter.AuthorityEntry(domain, port));
+        when(pm.getAllIntentFilters(TEST_PACKAGE)).thenReturn(Arrays.asList(filter));
+
+        assertThat(DomainUrlsUtils.getHandledDomains(pm, TEST_PACKAGE)).isEmpty();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppEntryListManagerTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppEntryListManagerTest.java
new file mode 100644
index 0000000..96143f9
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppEntryListManagerTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Looper;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowApplicationsState;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit test for {@link AppEntryListManager}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationsState.class})
+public class AppEntryListManagerTest {
+
+    @Mock
+    private ApplicationsState mApplicationsState;
+    @Mock
+    private ApplicationsState.Session mSession;
+    @Mock
+    private AppEntryListManager.ExtraInfoBridge mExtraInfoBridge;
+    @Mock
+    private AppEntryListManager.AppFilterProvider mFilterProvider;
+    @Mock
+    private AppEntryListManager.Callback mCallback;
+    @Captor
+    private ArgumentCaptor<ApplicationsState.Callbacks> mSessionCallbacksCaptor;
+    @Captor
+    private ArgumentCaptor<List<AppEntry>> mEntriesCaptor;
+
+    private AppEntryListManager mAppEntryListManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplicationsState.setInstance(mApplicationsState);
+        when(mApplicationsState.newSession(mSessionCallbacksCaptor.capture())).thenReturn(mSession);
+        when(mApplicationsState.getBackgroundLooper()).thenReturn(Looper.getMainLooper());
+
+        mAppEntryListManager = new AppEntryListManager(RuntimeEnvironment.application);
+        mAppEntryListManager.init(mExtraInfoBridge, mFilterProvider, mCallback);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationsState.reset();
+    }
+
+    @Test
+    public void start_resumesSession() {
+        mAppEntryListManager.start();
+
+        verify(mSession).onResume();
+    }
+
+    @Test
+    public void onPackageListChanged_loadsExtraInfo() {
+        mSessionCallbacksCaptor.getValue().onPackageListChanged();
+
+        verify(mExtraInfoBridge).loadExtraInfo(any());
+    }
+
+    @Test
+    public void onLoadEntriesComplete_loadsExtraInfo() {
+        mSessionCallbacksCaptor.getValue().onLoadEntriesCompleted();
+
+        verify(mExtraInfoBridge).loadExtraInfo(any());
+    }
+
+    @Test
+    public void stop_pausesSession() {
+        mAppEntryListManager.stop();
+
+        verify(mSession).onPause();
+    }
+
+    @Test
+    public void destroy_destroysSession() {
+        mAppEntryListManager.destroy();
+
+        verify(mSession).onDestroy();
+    }
+
+    @Test
+    public void forceUpdate_loadsExtraInfo() {
+        ArrayList<AppEntry> entries = new ArrayList<>();
+        entries.add(mock(AppEntry.class));
+        when(mSession.getAllApps()).thenReturn(entries);
+
+        mAppEntryListManager.forceUpdate();
+
+        verify(mExtraInfoBridge).loadExtraInfo(entries);
+    }
+
+    @Test
+    public void forceUpdate_forEntry_loadsExtraInfo() {
+        AppEntry entry = mock(AppEntry.class);
+
+        mAppEntryListManager.forceUpdate(entry);
+
+        verify(mExtraInfoBridge).loadExtraInfo(mEntriesCaptor.capture());
+        assertThat(mEntriesCaptor.getValue()).containsExactly(entry);
+    }
+
+    @Test
+    public void loadingFinished_rebuildsSession() {
+        ApplicationsState.AppFilter appFilter = mock(ApplicationsState.AppFilter.class);
+        when(mFilterProvider.getAppFilter()).thenReturn(appFilter);
+
+        mSessionCallbacksCaptor.getValue().onLoadEntriesCompleted();
+
+        verify(mSession).rebuild(eq(appFilter),
+                eq(ApplicationsState.ALPHA_COMPARATOR), /* foreground= */ eq(false));
+    }
+
+    @Test
+    public void onRebuildComplete_callsCallback() {
+        ArrayList<AppEntry> entries = new ArrayList<>();
+        entries.add(mock(AppEntry.class));
+
+        mSessionCallbacksCaptor.getValue().onRebuildComplete(entries);
+
+        verify(mCallback).onAppEntryListChanged(entries);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceControllerTest.java
new file mode 100644
index 0000000..b8e70f3
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceControllerTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Looper;
+import android.os.RemoteException;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAppOpsManager;
+import com.android.car.settings.testutils.ShadowApplicationsState;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Unit test for {@link AppOpsPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowAppOpsManager.class, ShadowApplicationsState.class})
+public class AppOpsPreferenceControllerTest {
+
+    private static final int APP_OP_CODE = AppOpsManager.OP_WRITE_SETTINGS;
+    private static final String PERMISSION = Manifest.permission.WRITE_SETTINGS;
+    private static final int NEGATIVE_MODE = AppOpsManager.MODE_ERRORED;
+
+    @Mock
+    private AppEntryListManager mAppEntryListManager;
+    @Mock
+    private ApplicationsState mApplicationsState;
+    @Captor
+    private ArgumentCaptor<AppEntryListManager.Callback> mCallbackCaptor;
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<AppOpsPreferenceController> mControllerHelper;
+    private AppOpsPreferenceController mController;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplicationsState.setInstance(mApplicationsState);
+        when(mApplicationsState.getBackgroundLooper()).thenReturn(Looper.getMainLooper());
+
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AppOpsPreferenceController.class);
+        mController = mControllerHelper.getController();
+        mController.init(APP_OP_CODE, PERMISSION, NEGATIVE_MODE);
+        mController.mAppEntryListManager = mAppEntryListManager;
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        verify(mAppEntryListManager).init(any(AppStateAppOpsBridge.class), any(),
+                mCallbackCaptor.capture());
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationsState.reset();
+    }
+
+    @Test
+    public void checkInitialized_noOpCode_throwsIllegalStateException() {
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AppOpsPreferenceController.class);
+        mController = mControllerHelper.getController();
+
+        mController.init(AppOpsManager.OP_NONE, PERMISSION, NEGATIVE_MODE);
+
+        assertThrows(IllegalStateException.class,
+                () -> mControllerHelper.setPreference(mPreferenceGroup));
+    }
+
+    @Test
+    public void checkInitialized_noPermission_throwsIllegalStateException() {
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AppOpsPreferenceController.class);
+        mController = mControllerHelper.getController();
+
+        mController.init(APP_OP_CODE, /* permission= */ null, NEGATIVE_MODE);
+
+        assertThrows(IllegalStateException.class,
+                () -> mControllerHelper.setPreference(mPreferenceGroup));
+    }
+
+    @Test
+    public void checkInitialized_noNegativeOpMode_throwsIllegalStateException() {
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AppOpsPreferenceController.class);
+        mController = mControllerHelper.getController();
+
+        mController.init(APP_OP_CODE, PERMISSION, AppOpsManager.MODE_DEFAULT);
+
+        assertThrows(IllegalStateException.class,
+                () -> mControllerHelper.setPreference(mPreferenceGroup));
+    }
+
+    @Test
+    public void onStart_startsListManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mAppEntryListManager).start();
+    }
+
+    @Test
+    public void onStop_stopsListManager() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mAppEntryListManager).stop();
+    }
+
+    @Test
+    public void onDestroy_destroysListManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        verify(mAppEntryListManager).destroy();
+    }
+
+    @Test
+    public void onAppEntryListChanged_addsPreferencesForEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        List<AppEntry> entries = Arrays.asList(
+                createAppEntry("test.package", /* uid= */ 1, /* isOpPermissible= */ true),
+                createAppEntry("another.test.package", /* uid= */ 2, /* isOpPermissible= */ false));
+
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+        assertThat(((TwoStatePreference) mPreferenceGroup.getPreference(0)).isChecked()).isTrue();
+        assertThat(((TwoStatePreference) mPreferenceGroup.getPreference(1)).isChecked()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChange_checkedState_setsAppOpModeAllowed() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        String packageName = "test.package";
+        int uid = 1;
+        List<AppEntry> entries = Collections.singletonList(
+                createAppEntry(packageName, uid, /* isOpPermissible= */ false));
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+        TwoStatePreference appPref = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+
+        appPref.performClick();
+
+        assertThat(getShadowAppOpsManager().getMode(APP_OP_CODE, uid, packageName)).isEqualTo(
+                AppOpsManager.MODE_ALLOWED);
+    }
+
+    @Test
+    public void onPreferenceChange_uncheckedState_setsNegativeAppOpMode() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        String packageName = "test.package";
+        int uid = 1;
+        List<AppEntry> entries = Collections.singletonList(
+                createAppEntry(packageName, uid, /* isOpPermissible= */ true));
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+        TwoStatePreference appPref = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+
+        appPref.performClick();
+
+        assertThat(getShadowAppOpsManager().getMode(APP_OP_CODE, uid, packageName)).isEqualTo(
+                NEGATIVE_MODE);
+    }
+
+    @Test
+    public void onPreferenceChange_updatesEntry() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        List<AppEntry> entries = Collections.singletonList(
+                createAppEntry("test.package", /* uid= */ 1, /* isOpPermissible= */ false));
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+        TwoStatePreference appPref = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+
+        appPref.performClick();
+
+        verify(mAppEntryListManager).forceUpdate(entries.get(0));
+    }
+
+    @Test
+    public void showSystem_updatesEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mController.setShowSystem(true);
+
+        verify(mAppEntryListManager).forceUpdate();
+    }
+
+    @Test
+    public void appFilter_showingSystemApps_keepsSystemEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mController.setShowSystem(true);
+        ArgumentCaptor<AppEntryListManager.AppFilterProvider> filterCaptor =
+                ArgumentCaptor.forClass(AppEntryListManager.AppFilterProvider.class);
+        verify(mAppEntryListManager).init(any(), filterCaptor.capture(), any());
+        ApplicationsState.AppFilter filter = filterCaptor.getValue().getAppFilter();
+
+        AppEntry systemApp = createAppEntry("test.package", /* uid= */ 1, /* isOpPermissible= */
+                false);
+        systemApp.info.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+        assertThat(filter.filterApp(systemApp)).isTrue();
+    }
+
+    @Test
+    public void appFilter_notShowingSystemApps_removesSystemEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        // Not showing system by default.
+        ArgumentCaptor<AppEntryListManager.AppFilterProvider> filterCaptor =
+                ArgumentCaptor.forClass(AppEntryListManager.AppFilterProvider.class);
+        verify(mAppEntryListManager).init(any(), filterCaptor.capture(), any());
+        ApplicationsState.AppFilter filter = filterCaptor.getValue().getAppFilter();
+
+        AppEntry systemApp = createAppEntry("test.package", /* uid= */ 1, /* isOpPermissible= */
+                false);
+        systemApp.info.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+        assertThat(filter.filterApp(systemApp)).isFalse();
+    }
+
+    @Test
+    public void appFilter_removesNullExtraInfoEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ArgumentCaptor<AppEntryListManager.AppFilterProvider> filterCaptor =
+                ArgumentCaptor.forClass(AppEntryListManager.AppFilterProvider.class);
+        verify(mAppEntryListManager).init(any(), filterCaptor.capture(), any());
+        ApplicationsState.AppFilter filter = filterCaptor.getValue().getAppFilter();
+
+        AppEntry appEntry = createAppEntry("test.package", /* uid= */ 1, /* isOpPermissible= */
+                false);
+        appEntry.extraInfo = null;
+
+        assertThat(filter.filterApp(appEntry)).isFalse();
+    }
+
+    private AppEntry createAppEntry(String packageName, int uid, boolean isOpPermissible) {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = packageName;
+        info.uid = uid;
+
+        AppStateAppOpsBridge.PermissionState extraInfo = mock(
+                AppStateAppOpsBridge.PermissionState.class);
+        when(extraInfo.isPermissible()).thenReturn(isOpPermissible);
+
+        AppEntry appEntry = mock(AppEntry.class);
+        appEntry.info = info;
+        appEntry.label = packageName;
+        appEntry.extraInfo = extraInfo;
+
+        return appEntry;
+    }
+
+    private ShadowAppOpsManager getShadowAppOpsManager() {
+        return Shadow.extract(mContext.getSystemService(Context.APP_OPS_SERVICE));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStateAppOpsBridgeTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStateAppOpsBridgeTest.java
new file mode 100644
index 0000000..718edde
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStateAppOpsBridgeTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.applications.specialaccess.AppStateAppOpsBridge.PermissionState;
+import com.android.car.settings.testutils.ShadowAppOpsManager;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.AdditionalMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowUserManager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Unit test for {@link AppStateAppOpsBridge}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowAppOpsManager.class})
+public class AppStateAppOpsBridgeTest {
+
+    private static final int APP_OP_CODE = AppOpsManager.OP_WRITE_SETTINGS;
+    private static final String PERMISSION = Manifest.permission.WRITE_SETTINGS;
+
+    @Mock
+    private IPackageManager mIPackageManager;
+    @Mock
+    private ParceledListSlice<PackageInfo> mParceledPackages;
+    @Mock
+    private ParceledListSlice<PackageInfo> mParceledPackagesOtherProfile;
+
+    private List<PackageInfo> mPackages;
+
+    private Context mContext;
+    private AppOpsManager mAppOpsManager;
+    private AppStateAppOpsBridge mBridge;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        mPackages = new ArrayList<>();
+        when(mIPackageManager.getPackagesHoldingPermissions(
+                AdditionalMatchers.aryEq(new String[]{PERMISSION}),
+                eq(PackageManager.GET_PERMISSIONS),
+                eq(UserHandle.myUserId())))
+                .thenReturn(mParceledPackages);
+        when(mParceledPackages.getList()).thenReturn(mPackages);
+
+        mContext = RuntimeEnvironment.application;
+        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mBridge = new AppStateAppOpsBridge(mContext, APP_OP_CODE, PERMISSION, mIPackageManager);
+    }
+
+    @Test
+    public void androidPackagesIgnored() throws RemoteException {
+        String packageName = "android";
+        int uid = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo = createPackageInfo(packageName, uid);
+        addPackageWithPermission(packageInfo, AppOpsManager.MODE_ALLOWED);
+        AppEntry entry = createAppEntry(packageInfo);
+
+        mBridge.loadExtraInfo(Collections.singletonList(entry));
+
+        assertThat(entry.extraInfo).isNull();
+    }
+
+    @Test
+    public void selfPackageIgnored() throws RemoteException {
+        String packageName = mContext.getPackageName();
+        int uid = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo = createPackageInfo(packageName, uid);
+        addPackageWithPermission(packageInfo, AppOpsManager.MODE_ALLOWED);
+        AppEntry entry = createAppEntry(packageInfo);
+
+        mBridge.loadExtraInfo(Collections.singletonList(entry));
+
+        assertThat(entry.extraInfo).isNull();
+    }
+
+    @Test
+    public void packagesNotRequestingPermissionIgnored() throws RemoteException {
+        String packageName = "test.package";
+        int uid = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo = createPackageInfo(packageName, uid);
+        packageInfo.requestedPermissions = null;
+        mPackages.add(packageInfo);
+        when(mIPackageManager.isPackageAvailable(packageInfo.packageName,
+                UserHandle.myUserId())).thenReturn(true);
+        AppEntry entry = createAppEntry(packageInfo);
+
+        mBridge.loadExtraInfo(Collections.singletonList(entry));
+
+        assertThat(entry.extraInfo).isNull();
+    }
+
+    @Test
+    public void unavailablePackageIgnored() throws RemoteException {
+        String packageName = "test.package";
+        int uid = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo = createPackageInfo(packageName, uid);
+        addPackageWithPermission(packageInfo, AppOpsManager.MODE_ALLOWED);
+        when(mIPackageManager.isPackageAvailable(packageInfo.packageName,
+                UserHandle.myUserId())).thenReturn(false);
+        AppEntry entry = createAppEntry(packageInfo);
+
+        mBridge.loadExtraInfo(Collections.singletonList(entry));
+
+        assertThat(entry.extraInfo).isNull();
+    }
+
+    @Test
+    public void loadsAppOpsExtraInfo_modeAllowed_isPermissible() throws RemoteException {
+        String packageName = "test.package";
+        int uid = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo = createPackageInfo(packageName, uid);
+        addPackageWithPermission(packageInfo, AppOpsManager.MODE_ALLOWED);
+        AppEntry entry = createAppEntry(packageInfo);
+        assertThat(entry.extraInfo).isNull();
+
+        mBridge.loadExtraInfo(Collections.singletonList(entry));
+
+        assertThat(entry.extraInfo).isNotNull();
+        assertThat(((PermissionState) entry.extraInfo).isPermissible()).isTrue();
+    }
+
+    @Test
+    public void loadsAppOpsExtraInfo_modeDefault_isPermissible() throws RemoteException {
+        String packageName = "test.package";
+        int uid = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo = createPackageInfo(packageName, uid);
+        addPackageWithPermission(packageInfo, AppOpsManager.MODE_DEFAULT);
+        AppEntry entry = createAppEntry(packageInfo);
+        assertThat(entry.extraInfo).isNull();
+
+        mBridge.loadExtraInfo(Collections.singletonList(entry));
+
+        assertThat(entry.extraInfo).isNotNull();
+        assertThat(((PermissionState) entry.extraInfo).isPermissible()).isTrue();
+    }
+
+    @Test
+    public void loadsAppOpsExtraInfo_modeIgnored_isNotPermissible() throws RemoteException {
+        String packageName = "test.package";
+        int uid = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo = createPackageInfo(packageName, uid);
+        addPackageWithPermission(packageInfo, AppOpsManager.MODE_IGNORED);
+        AppEntry entry = createAppEntry(packageInfo);
+        assertThat(entry.extraInfo).isNull();
+
+        mBridge.loadExtraInfo(Collections.singletonList(entry));
+
+        assertThat(entry.extraInfo).isNotNull();
+        assertThat(((PermissionState) entry.extraInfo).isPermissible()).isFalse();
+    }
+
+    @Test
+    public void loadsAppOpsExtraInfo_multipleApps() throws RemoteException {
+        String packageName1 = "test.package1";
+        int uid1 = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo1 = createPackageInfo(packageName1, uid1);
+        addPackageWithPermission(packageInfo1, AppOpsManager.MODE_ALLOWED);
+        AppEntry entry1 = createAppEntry(packageInfo1);
+
+        String packageName2 = "test.package2";
+        int uid2 = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 2);
+        PackageInfo packageInfo2 = createPackageInfo(packageName2, uid2);
+        addPackageWithPermission(packageInfo2, AppOpsManager.MODE_ALLOWED);
+        AppEntry entry2 = createAppEntry(packageInfo2);
+
+        mBridge.loadExtraInfo(Arrays.asList(entry1, entry2));
+
+        assertThat(entry1.extraInfo).isNotNull();
+        assertThat(entry2.extraInfo).isNotNull();
+    }
+
+    @Test
+    public void loadsAppOpExtraInfo_multipleProfiles() throws RemoteException {
+        String packageName1 = "test.package1";
+        int uid1 = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo1 = createPackageInfo(packageName1, uid1);
+        addPackageWithPermission(packageInfo1, AppOpsManager.MODE_ALLOWED);
+        AppEntry entry1 = createAppEntry(packageInfo1);
+
+        // Add a package for another profile.
+        int otherUserId = UserHandle.myUserId() + 1;
+        String packageName2 = "test.package2";
+        int uid2 = UserHandle.getUid(otherUserId, /* appId= */ 2);
+        PackageInfo packageInfo2 = createPackageInfo(packageName2, uid2);
+        when(mIPackageManager.getPackagesHoldingPermissions(
+                AdditionalMatchers.aryEq(new String[]{PERMISSION}),
+                eq(PackageManager.GET_PERMISSIONS),
+                eq(otherUserId)))
+                .thenReturn(mParceledPackagesOtherProfile);
+        when(mParceledPackagesOtherProfile.getList()).thenReturn(
+                Collections.singletonList(packageInfo2));
+        when(mIPackageManager.isPackageAvailable(packageInfo2.packageName,
+                otherUserId)).thenReturn(true);
+        mAppOpsManager.setMode(APP_OP_CODE, packageInfo2.applicationInfo.uid,
+                packageInfo2.packageName, AppOpsManager.MODE_ALLOWED);
+        AppEntry entry2 = createAppEntry(packageInfo2);
+
+        getShadowUserManager().addUserProfile(UserHandle.of(otherUserId));
+        // Recreate the bridge so it has all user profiles.
+        mBridge = new AppStateAppOpsBridge(mContext, APP_OP_CODE, PERMISSION, mIPackageManager);
+
+        mBridge.loadExtraInfo(Arrays.asList(entry1, entry2));
+
+        assertThat(entry1.extraInfo).isNotNull();
+        assertThat(entry2.extraInfo).isNotNull();
+    }
+
+    @Test
+    public void appEntryNotIncluded_extraInfoCleared() {
+        String packageName = "test.package";
+        int uid = UserHandle.getUid(UserHandle.myUserId(), /* appId= */ 1);
+        PackageInfo packageInfo = createPackageInfo(packageName, uid);
+        AppEntry entry = createAppEntry(packageInfo);
+        entry.extraInfo = new Object();
+
+        mBridge.loadExtraInfo(Collections.singletonList(entry));
+
+        assertThat(entry.extraInfo).isNull();
+    }
+
+    private PackageInfo createPackageInfo(String packageName, int uid) {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = packageName;
+        applicationInfo.uid = uid;
+
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = packageName;
+        packageInfo.applicationInfo = applicationInfo;
+        packageInfo.requestedPermissions = new String[]{PERMISSION};
+
+        return packageInfo;
+    }
+
+    private void addPackageWithPermission(PackageInfo packageInfo, int mode)
+            throws RemoteException {
+        mPackages.add(packageInfo);
+        when(mIPackageManager.isPackageAvailable(packageInfo.packageName,
+                UserHandle.myUserId())).thenReturn(true);
+        mAppOpsManager.setMode(APP_OP_CODE, packageInfo.applicationInfo.uid,
+                packageInfo.packageName, mode);
+    }
+
+    private AppEntry createAppEntry(PackageInfo packageInfo) {
+        AppEntry appEntry = mock(AppEntry.class);
+        appEntry.info = packageInfo.applicationInfo;
+        return appEntry;
+    }
+
+    private ShadowUserManager getShadowUserManager() {
+        return Shadows.shadowOf(UserManager.get(mContext));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridgeTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridgeTest.java
new file mode 100644
index 0000000..382917c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridgeTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.os.RemoteException;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+/** Unit test for {@link AppStatePremiumSmsBridge}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AppStatePremiumSmsBridgeTest {
+
+    @Mock
+    private ISms mSmsManager;
+    private AppStatePremiumSmsBridge mBridge;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mBridge = new AppStatePremiumSmsBridge(mSmsManager);
+    }
+
+    @Test
+    public void loadExtraInfo() throws RemoteException {
+        String package1 = "test.package1";
+        AppEntry appEntry1 = createAppEntry(package1);
+        int value1 = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW;
+        when(mSmsManager.getPremiumSmsPermission(package1)).thenReturn(value1);
+
+        String package2 = "test.package2";
+        AppEntry appEntry2 = createAppEntry(package2);
+        int value2 = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW;
+        when(mSmsManager.getPremiumSmsPermission(package2)).thenReturn(value2);
+
+        mBridge.loadExtraInfo(Arrays.asList(appEntry1, appEntry2));
+
+        assertThat(appEntry1.extraInfo).isEqualTo(value1);
+        assertThat(appEntry2.extraInfo).isEqualTo(value2);
+    }
+
+    private AppEntry createAppEntry(String packageName) {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = packageName;
+
+        AppEntry appEntry = mock(AppEntry.class);
+        appEntry.info = info;
+        appEntry.label = packageName;
+
+        return appEntry;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsPreferenceControllerTest.java
new file mode 100644
index 0000000..146c978
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/DirectoryAccessDetailsPreferenceControllerTest.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.AUTHORITY;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.COL_GRANTED;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_DIRECTORY;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_GRANTED;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_PACKAGE;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_VOLUME_UUID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowStorageManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.fakes.BaseCursor;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import java.util.Arrays;
+
+/** Unit test for {@link DirectoryAccessDetailsPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowStorageManager.class})
+public class DirectoryAccessDetailsPreferenceControllerTest {
+
+    private static final String PACKAGE = "test.package";
+
+    @Mock
+    private BaseCursor mCursor;
+    private Uri mProviderUri;
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<DirectoryAccessDetailsPreferenceController>
+            mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DirectoryAccessDetailsPreferenceController.class);
+        mControllerHelper.getController().setPackage(PACKAGE);
+        mControllerHelper.setPreference(mPreferenceGroup);
+
+        mProviderUri = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(AUTHORITY)
+                .appendPath(TABLE_PERMISSIONS)
+                .appendPath("*")
+                .build();
+        getShadowContentResolver().setCursor(mProviderUri, mCursor);
+    }
+
+    @Test
+    public void checkInitialized_noPackageSet_throwsIllegalStateException() {
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DirectoryAccessDetailsPreferenceController.class);
+
+        assertThrows(IllegalStateException.class,
+                () -> mControllerHelper.setPreference(new LogicalPreferenceGroup(mContext)));
+    }
+
+    @Test
+    public void onCreate_primaryStoragePermission_addsPreference() {
+        when(mCursor.getCount()).thenReturn(1);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        // Null uuid for primary storage.
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(null);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(
+                Environment.DIRECTORY_PICTURES);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onCreate_primaryStoragePermission_granted_setsChecked() {
+        when(mCursor.getCount()).thenReturn(1);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        // Null uuid for primary storage.
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(null);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(
+                Environment.DIRECTORY_PICTURES);
+        when(mCursor.getInt(TABLE_PERMISSIONS_COL_GRANTED)).thenReturn(1);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        TwoStatePreference pref = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+        assertThat(pref.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onCreate_primaryStoragePermission_setsPreferenceTitleToDirectory() {
+        String dirName = Environment.DIRECTORY_PICTURES;
+        when(mCursor.getCount()).thenReturn(1);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        // Null uuid for primary storage.
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(null);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(dirName);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        Preference pref = mPreferenceGroup.getPreference(0);
+        assertThat(pref.getTitle()).isEqualTo(dirName);
+    }
+
+    @Test
+    public void onCreate_externalVolumePermission_wholeVolume_addsPreference() {
+        String name = "external volume";
+        String uuid = "external uuid";
+        VolumeInfo primaryStorage = mock(VolumeInfo.class);
+        VolumeInfo external = mock(VolumeInfo.class);
+        when(external.getFsUuid()).thenReturn(uuid);
+        getShadowStorageManager().setVolumes(Arrays.asList(primaryStorage, external));
+        getShadowStorageManager().setBestVolumeDescription(external, name);
+
+        when(mCursor.getCount()).thenReturn(1);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(uuid);
+        // Null directory indicates access for whole volume.
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(null);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getTitle()).isEqualTo(name);
+    }
+
+    @Test
+    public void onCreate_externalVolumePermission_directory_addsWholeAndDirectoryPreferences() {
+        String name = "external volume";
+        String uuid = "external uuid";
+        String dirName = Environment.DIRECTORY_PICTURES;
+        VolumeInfo primaryStorage = mock(VolumeInfo.class);
+        VolumeInfo external = mock(VolumeInfo.class);
+        when(external.getFsUuid()).thenReturn(uuid);
+        getShadowStorageManager().setVolumes(Arrays.asList(primaryStorage, external));
+        getShadowStorageManager().setBestVolumeDescription(external, name);
+
+        when(mCursor.getCount()).thenReturn(2);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(uuid);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(null).thenReturn(
+                dirName);
+        // Root not granted.
+        when(mCursor.getInt(TABLE_PERMISSIONS_COL_GRANTED)).thenReturn(0);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+        assertThat(mPreferenceGroup.getPreference(0).getTitle()).isEqualTo(name);
+        // External volume directory preference should have name of volume and directory.
+        assertThat(mPreferenceGroup.getPreference(1).getTitle().toString()).contains(name);
+        assertThat(mPreferenceGroup.getPreference(1).getTitle().toString()).contains(dirName);
+    }
+
+    @Test
+    public void onCreate_externalVolumePermission_rootGranted_hidesDirectoryPreference() {
+        String name = "external volume";
+        String uuid = "external uuid";
+        String dirName = Environment.DIRECTORY_PICTURES;
+        VolumeInfo primaryStorage = mock(VolumeInfo.class);
+        VolumeInfo external = mock(VolumeInfo.class);
+        when(external.getFsUuid()).thenReturn(uuid);
+        getShadowStorageManager().setVolumes(Arrays.asList(primaryStorage, external));
+        getShadowStorageManager().setBestVolumeDescription(external, name);
+
+        when(mCursor.getCount()).thenReturn(2);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(uuid);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(null).thenReturn(
+                dirName);
+        // Root granted.
+        when(mCursor.getInt(TABLE_PERMISSIONS_COL_GRANTED)).thenReturn(1);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getTitle()).isEqualTo(name);
+    }
+
+    @Test
+    public void onPreferenceClicked_primaryStorage_updatesContentProvider() {
+        String dirName = Environment.DIRECTORY_PICTURES;
+        when(mCursor.getCount()).thenReturn(1);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        // Null uuid for primary storage.
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(null);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(dirName);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        Preference pref = mPreferenceGroup.getPreference(0);
+
+        pref.performClick();
+
+        assertThat(getShadowContentResolver().getUpdateStatements()).hasSize(1);
+        ShadowContentResolver.UpdateStatement updateStatement =
+                getShadowContentResolver().getUpdateStatements().get(0);
+        assertThat(updateStatement.getUri()).isEqualTo(mProviderUri);
+        assertThat(updateStatement.getContentValues().get(COL_GRANTED)).isEqualTo(true);
+        assertThat(updateStatement.getSelectionArgs()).isEqualTo(
+                new String[]{PACKAGE, null, dirName});
+    }
+
+    @Test
+    public void onPreferenceClicked_externalStorage_updatesContentProvider() {
+        String uuid = "external uuid";
+        VolumeInfo primaryStorage = mock(VolumeInfo.class);
+        VolumeInfo external = mock(VolumeInfo.class);
+        when(external.getFsUuid()).thenReturn(uuid);
+        getShadowStorageManager().setVolumes(Arrays.asList(primaryStorage, external));
+        getShadowStorageManager().setBestVolumeDescription(external, "external volume");
+
+        when(mCursor.getCount()).thenReturn(1);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(uuid);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(null);
+        // Root granted
+        when(mCursor.getInt(TABLE_PERMISSIONS_COL_GRANTED)).thenReturn(1);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        Preference pref = mPreferenceGroup.getPreference(0);
+
+        pref.performClick();
+
+        assertThat(getShadowContentResolver().getUpdateStatements()).hasSize(1);
+        ShadowContentResolver.UpdateStatement updateStatement =
+                getShadowContentResolver().getUpdateStatements().get(0);
+        assertThat(updateStatement.getUri()).isEqualTo(mProviderUri);
+        assertThat(updateStatement.getContentValues().get(COL_GRANTED)).isEqualTo(false);
+        assertThat(updateStatement.getSelectionArgs()).isEqualTo(
+                new String[]{PACKAGE, uuid, null});
+    }
+
+    @Test
+    public void onPreferenceClicked_externalStorage_directory_updatesContentProvider() {
+        String uuid = "external uuid";
+        String dirName = Environment.DIRECTORY_PICTURES;
+        VolumeInfo primaryStorage = mock(VolumeInfo.class);
+        VolumeInfo external = mock(VolumeInfo.class);
+        when(external.getFsUuid()).thenReturn(uuid);
+        getShadowStorageManager().setVolumes(Arrays.asList(primaryStorage, external));
+        getShadowStorageManager().setBestVolumeDescription(external, "external volume");
+
+        when(mCursor.getCount()).thenReturn(2);
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(uuid);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(null).thenReturn(
+                dirName);
+        // Root not granted.
+        when(mCursor.getInt(TABLE_PERMISSIONS_COL_GRANTED)).thenReturn(0);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        Preference pref = mPreferenceGroup.getPreference(1);
+
+        pref.performClick();
+
+        assertThat(getShadowContentResolver().getUpdateStatements()).hasSize(1);
+        ShadowContentResolver.UpdateStatement updateStatement =
+                getShadowContentResolver().getUpdateStatements().get(0);
+        assertThat(updateStatement.getUri()).isEqualTo(mProviderUri);
+        assertThat(updateStatement.getContentValues().get(COL_GRANTED)).isEqualTo(true);
+        assertThat(updateStatement.getSelectionArgs()).isEqualTo(
+                new String[]{PACKAGE, uuid, dirName});
+    }
+
+    @Test
+    public void onPreferenceClicked_refreshesUi() {
+        String uuid = "external uuid";
+        String dirName = Environment.DIRECTORY_PICTURES;
+        VolumeInfo primaryStorage = mock(VolumeInfo.class);
+        VolumeInfo external = mock(VolumeInfo.class);
+        when(external.getFsUuid()).thenReturn(uuid);
+        getShadowStorageManager().setVolumes(Arrays.asList(primaryStorage, external));
+        getShadowStorageManager().setBestVolumeDescription(external, "external volume");
+
+        when(mCursor.getCount()).thenReturn(2);
+        // Setup for two iterations over cursor with two rows.
+        when(mCursor.moveToNext()).thenReturn(true).thenReturn(true).thenReturn(false).thenReturn(
+                true).thenReturn(true).thenReturn(false);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_PACKAGE)).thenReturn(PACKAGE);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID)).thenReturn(uuid);
+        when(mCursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY)).thenReturn(null).thenReturn(
+                dirName).thenReturn(null).thenReturn(dirName);
+        // Root not granted, dir not granted -> root granted, dir not explicitly granted.
+        when(mCursor.getInt(TABLE_PERMISSIONS_COL_GRANTED)).thenReturn(0).thenReturn(0).thenReturn(
+                1).thenReturn(0);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+        Preference pref = mPreferenceGroup.getPreference(0);
+
+        pref.performClick();
+
+        // Granting access to root should hide the directory preference on refresh.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    private ShadowContentResolver getShadowContentResolver() {
+        return Shadows.shadowOf(mContext.getContentResolver());
+    }
+
+    private ShadowStorageManager getShadowStorageManager() {
+        return Shadow.extract(mContext.getSystemService(StorageManager.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/DirectoryAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/DirectoryAccessPreferenceControllerTest.java
new file mode 100644
index 0000000..3da0a44
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/DirectoryAccessPreferenceControllerTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.AUTHORITY;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES;
+import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COL_PACKAGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.Looper;
+
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowApplicationsState;
+import com.android.car.settings.testutils.ShadowContentResolver;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.fakes.BaseCursor;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Unit test for {@link DirectoryAccessPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationsState.class, ShadowContentResolver.class})
+public class DirectoryAccessPreferenceControllerTest {
+
+    @Mock
+    private AppEntryListManager mAppEntryListManager;
+    @Mock
+    private ApplicationsState mApplicationsState;
+    @Captor
+    private ArgumentCaptor<AppEntryListManager.AppFilterProvider> mFilterCaptor;
+    @Captor
+    private ArgumentCaptor<AppEntryListManager.Callback> mCallbackCaptor;
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<DirectoryAccessPreferenceController> mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplicationsState.setInstance(mApplicationsState);
+        when(mApplicationsState.getBackgroundLooper()).thenReturn(Looper.getMainLooper());
+
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DirectoryAccessPreferenceController.class, mPreferenceGroup);
+        mControllerHelper.getController().mAppEntryListManager = mAppEntryListManager;
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        verify(mAppEntryListManager).init(isNull(), mFilterCaptor.capture(),
+                mCallbackCaptor.capture());
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationsState.reset();
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void onStart_startsListManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mAppEntryListManager).start();
+    }
+
+    @Test
+    public void onStop_stopsListManager() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mAppEntryListManager).stop();
+    }
+
+    @Test
+    public void onDestroy_destroysListManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        verify(mAppEntryListManager).destroy();
+    }
+
+    @Test
+    public void onAppEntryListChanged_addsPreferencesForEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        List<ApplicationsState.AppEntry> entries = Arrays.asList(
+                createAppEntry("test.package", /* uid= */ 1),
+                createAppEntry("another.test.package", /* uid= */ 2));
+
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void onPreferenceClicked_launchesDetailsFragmentForPackage() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        String packageName = "test.package";
+        List<ApplicationsState.AppEntry> entries = Collections.singletonList(
+                createAppEntry(packageName, /* uid= */ 1));
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+        Preference appPref = mPreferenceGroup.getPreference(0);
+
+        appPref.performClick();
+
+        ArgumentCaptor<Fragment> fragmentCaptor = ArgumentCaptor.forClass(Fragment.class);
+        verify(mControllerHelper.getMockFragmentController()).launchFragment(
+                fragmentCaptor.capture());
+        assertThat(fragmentCaptor.getValue()).isInstanceOf(DirectoryAccessDetailsFragment.class);
+        assertThat(fragmentCaptor.getValue().getArguments().getString(
+                DirectoryAccessDetailsFragment.ARG_PACKAGE_NAME)).isEqualTo(packageName);
+    }
+
+    @Test
+    public void appFilter_removesPackagesNotInScopedAccessProvider() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        String includedPackage = "test.package";
+        String excludedPackage = "test.package2";
+
+        BaseCursor cursor = mock(BaseCursor.class);
+        when(cursor.getCount()).thenReturn(1);
+        when(cursor.moveToNext()).thenReturn(true).thenReturn(false);
+        when(cursor.getString(TABLE_PACKAGES_COL_PACKAGE)).thenReturn(includedPackage);
+
+        Uri providerUri = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(AUTHORITY)
+                .appendPath(TABLE_PACKAGES)
+                .appendPath("*")
+                .build();
+        getShadowContentResolver().setCursor(providerUri, cursor);
+
+        ApplicationsState.AppFilter filter = mFilterCaptor.getValue().getAppFilter();
+        filter.init(mContext);
+
+        assertThat(filter.filterApp(createAppEntry(includedPackage, /* uid= */ 1))).isTrue();
+        assertThat(filter.filterApp(createAppEntry(excludedPackage, /* uid= */ 2))).isFalse();
+    }
+
+    private ApplicationsState.AppEntry createAppEntry(String packageName, int uid) {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = packageName;
+        info.uid = uid;
+
+        ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+        appEntry.info = info;
+        appEntry.label = packageName;
+
+        return appEntry;
+    }
+
+    private ShadowContentResolver getShadowContentResolver() {
+        return Shadow.extract(mContext.getContentResolver());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/NotificationAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/NotificationAccessPreferenceControllerTest.java
new file mode 100644
index 0000000..a8cd5c9
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/NotificationAccessPreferenceControllerTest.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.AutomaticZenRule;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.service.notification.NotificationListenerService;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowNotificationManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link NotificationAccessPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class, ShadowNotificationManager.class})
+public class NotificationAccessPreferenceControllerTest {
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<NotificationAccessPreferenceController>
+            mControllerHelper;
+    private NotificationAccessPreferenceController mController;
+
+    private ServiceInfo mListenerServiceInfo;
+    private ComponentName mListenerComponent;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                NotificationAccessPreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+
+        mListenerServiceInfo = new ServiceInfo();
+        mListenerServiceInfo.permission = Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
+        mListenerServiceInfo.packageName = "com.android.test.package";
+        mListenerServiceInfo.name = "SomeListenerService";
+        mListenerServiceInfo.nonLocalizedLabel = "label";
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = 123;
+        mListenerServiceInfo.applicationInfo = applicationInfo;
+
+        mListenerComponent = new ComponentName(mListenerServiceInfo.packageName,
+                mListenerServiceInfo.name);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationPackageManager.reset();
+    }
+
+    @Test
+    public void onStart_loadsListenerServices() {
+        addNotificationListenerService(mListenerServiceInfo);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onStart_serviceAccessGranted_setsPreferenceChecked() {
+        addNotificationListenerService(mListenerServiceInfo);
+        mContext.getSystemService(NotificationManager.class).setNotificationListenerAccessGranted(
+                mListenerComponent, /* granted= */ true);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        TwoStatePreference preference = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+        assertThat(preference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onStart_serviceAccessNotGranted_setsPreferenceUnchecked() {
+        addNotificationListenerService(mListenerServiceInfo);
+        mContext.getSystemService(NotificationManager.class).setNotificationListenerAccessGranted(
+                mListenerComponent, /* granted= */ false);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        TwoStatePreference preference = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+        assertThat(preference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void preferenceClicked_serviceAccessGranted_showsRevokeConfirmDialog() {
+        addNotificationListenerService(mListenerServiceInfo);
+        mContext.getSystemService(NotificationManager.class).setNotificationListenerAccessGranted(
+                mListenerComponent, /* granted= */ true);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        TwoStatePreference preference = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+
+        preference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(any(
+                ConfirmationDialogFragment.class),
+                eq(NotificationAccessPreferenceController.REVOKE_CONFIRM_DIALOG_TAG));
+    }
+
+    @Test
+    public void preferenceClicked_serviceAccessNotGranted_showsGrantConfirmDialog() {
+        addNotificationListenerService(mListenerServiceInfo);
+        mContext.getSystemService(NotificationManager.class).setNotificationListenerAccessGranted(
+                mListenerComponent, /* granted= */ false);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        TwoStatePreference preference = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+
+        preference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(any(
+                ConfirmationDialogFragment.class),
+                eq(NotificationAccessPreferenceController.GRANT_CONFIRM_DIALOG_TAG));
+    }
+
+    @Test
+    public void revokeConfirmed_revokesNotificationAccess() {
+        addNotificationListenerService(mListenerServiceInfo);
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        notificationManager.setNotificationListenerAccessGranted(
+                mListenerComponent, /* granted= */ true);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        TwoStatePreference preference = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+        preference.performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(NotificationAccessPreferenceController.REVOKE_CONFIRM_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(/* dialog= */ null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(notificationManager.isNotificationListenerAccessGranted(
+                mListenerComponent)).isFalse();
+    }
+
+    @Test
+    public void revokeConfirmed_notificationPolicyAccessNotGranted_removesAutomaticZenRules() {
+        addNotificationListenerService(mListenerServiceInfo);
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        notificationManager.setNotificationListenerAccessGranted(
+                mListenerComponent, /* granted= */ true);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        TwoStatePreference preference = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+        preference.performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(NotificationAccessPreferenceController.REVOKE_CONFIRM_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        // Add a rule to be removed on access revoke.
+        notificationManager.addAutomaticZenRule(mock(AutomaticZenRule.class));
+        assertThat(notificationManager.getAutomaticZenRules()).isNotEmpty();
+
+        notificationManager.setNotificationPolicyAccessGranted(
+                mListenerServiceInfo.packageName, /* granted= */ false);
+        dialogFragment.onClick(/* dialog= */ null, DialogInterface.BUTTON_POSITIVE);
+
+        Robolectric.flushBackgroundThreadScheduler();
+        Robolectric.flushForegroundThreadScheduler();
+
+        assertThat(notificationManager.getAutomaticZenRules()).isEmpty();
+    }
+
+    @Test
+    public void grantConfirmed_grantsNotificationAccess() {
+        addNotificationListenerService(mListenerServiceInfo);
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        notificationManager.setNotificationListenerAccessGranted(
+                mListenerComponent, /* granted= */ false);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        TwoStatePreference preference = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+        preference.performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(NotificationAccessPreferenceController.GRANT_CONFIRM_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(/* dialog= */ null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(notificationManager.isNotificationListenerAccessGranted(
+                mListenerComponent)).isTrue();
+    }
+
+    private void addNotificationListenerService(ServiceInfo serviceInfo) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        getShadowPackageManager().addResolveInfoForIntent(
+                new Intent(NotificationListenerService.SERVICE_INTERFACE), resolveInfo);
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = serviceInfo.packageName;
+        packageInfo.applicationInfo = serviceInfo.applicationInfo;
+        getShadowPackageManager().addPackage(packageInfo);
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceControllerTest.java
new file mode 100644
index 0000000..38c1678
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceControllerTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.applications.specialaccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Looper;
+import android.os.RemoteException;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowApplicationsState;
+import com.android.car.settings.testutils.ShadowISms;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Unit test for {@link PremiumSmsAccessPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationsState.class, ShadowISms.class})
+public class PremiumSmsAccessPreferenceControllerTest {
+
+    @Mock
+    private AppEntryListManager mAppEntryListManager;
+    @Mock
+    private ApplicationsState mApplicationsState;
+    @Mock
+    private ISms mISms;
+    @Captor
+    private ArgumentCaptor<AppEntryListManager.Callback> mCallbackCaptor;
+
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<PremiumSmsAccessPreferenceController> mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplicationsState.setInstance(mApplicationsState);
+        when(mApplicationsState.getBackgroundLooper()).thenReturn(Looper.getMainLooper());
+        ShadowISms.setISms(mISms);
+
+        Context context = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                PremiumSmsAccessPreferenceController.class, mPreferenceGroup);
+        mControllerHelper.getController().mAppEntryListManager = mAppEntryListManager;
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        verify(mAppEntryListManager).init(any(AppStatePremiumSmsBridge.class), any(),
+                mCallbackCaptor.capture());
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationsState.reset();
+        ShadowISms.reset();
+    }
+
+    @Test
+    public void onStart_startsListManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mAppEntryListManager).start();
+    }
+
+    @Test
+    public void onStop_stopsListManager() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mAppEntryListManager).stop();
+    }
+
+    @Test
+    public void onDestroy_destroysListManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        verify(mAppEntryListManager).destroy();
+    }
+
+    @Test
+    public void onAppEntryListChanged_addsPreferencesForEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        List<AppEntry> entries = Arrays.asList(
+                createAppEntry("test.package", /* uid= */ 1,
+                        SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW),
+                createAppEntry("another.test.package", /* uid= */ 2,
+                        SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW));
+
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+        assertThat(((ListPreference) mPreferenceGroup.getPreference(0)).getValue()).isEqualTo(
+                String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW));
+        assertThat(((ListPreference) mPreferenceGroup.getPreference(1)).getValue()).isEqualTo(
+                String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW));
+    }
+
+    @Test
+    public void onPreferenceChange_setsPremiumSmsPermission() throws RemoteException {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        String packageName = "test.package";
+        List<AppEntry> entries = Collections.singletonList(
+                createAppEntry(packageName, /* uid= */ 1,
+                        SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW));
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+        Preference appPref = mPreferenceGroup.getPreference(0);
+        int updatedValue = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER;
+
+        appPref.getOnPreferenceChangeListener().onPreferenceChange(appPref,
+                String.valueOf(updatedValue));
+
+        verify(mISms).setPremiumSmsPermission(packageName, updatedValue);
+    }
+
+    @Test
+    public void onPreferenceChange_updatesEntry() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        List<AppEntry> entries = Collections.singletonList(
+                createAppEntry("test.package", /* uid= */ 1,
+                        SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW));
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+        Preference appPref = mPreferenceGroup.getPreference(0);
+
+        appPref.getOnPreferenceChangeListener().onPreferenceChange(appPref,
+                String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER));
+
+        verify(mAppEntryListManager).forceUpdate(entries.get(0));
+    }
+
+    @Test
+    public void appFilter_removesUnknownStates() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ArgumentCaptor<AppEntryListManager.AppFilterProvider> filterCaptor =
+                ArgumentCaptor.forClass(AppEntryListManager.AppFilterProvider.class);
+        verify(mAppEntryListManager).init(any(), filterCaptor.capture(), any());
+        ApplicationsState.AppFilter filter = filterCaptor.getValue().getAppFilter();
+        AppEntry unknownStateApp = createAppEntry("test.package", /* uid= */ 1,
+                SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN);
+
+        assertThat(filter.filterApp(unknownStateApp)).isFalse();
+    }
+
+    private AppEntry createAppEntry(String packageName, int uid, int smsState) {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = packageName;
+        info.uid = uid;
+
+        AppEntry appEntry = mock(AppEntry.class);
+        appEntry.info = info;
+        appEntry.label = packageName;
+        appEntry.extraInfo = smsState;
+
+        return appEntry;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothAddressPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothAddressPreferenceControllerTest.java
new file mode 100644
index 0000000..eb35004
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothAddressPreferenceControllerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link BluetoothAddressPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothAddressPreferenceControllerTest {
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void refreshUi_setsAddress() {
+        Context context = RuntimeEnvironment.application;
+
+        // Make sure controller is available.
+        Shadows.shadowOf(context.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+        String address = "address";
+        getShadowBluetoothAdapter().setAddress(address);
+
+        // Construct controller.
+        Preference preference = new Preference(context);
+        PreferenceControllerTestHelper<BluetoothAddressPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(context,
+                        BluetoothAddressPreferenceController.class,
+                        preference);
+        controllerHelper.markState(Lifecycle.State.CREATED);
+
+        controllerHelper.getController().refreshUi();
+
+        assertThat(preference.getTitle()).isEqualTo(
+                context.getString(R.string.bluetooth_vehicle_mac_address, address));
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothBondedDevicesPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothBondedDevicesPreferenceControllerTest.java
new file mode 100644
index 0000000..e7f518a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothBondedDevicesPreferenceControllerTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Arrays;
+
+/** Unit test for {@link BluetoothBondedDevicesPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class BluetoothBondedDevicesPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private CachedBluetoothDevice mBondedCachedDevice;
+    @Mock
+    private BluetoothDevice mBondedDevice;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<BluetoothBondedDevicesPreferenceController>
+            mControllerHelper;
+    private BluetoothBondedDevicesPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        Context context = RuntimeEnvironment.application;
+
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */
+                null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        when(mBondedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBondedCachedDevice.getDevice()).thenReturn(mBondedDevice);
+        BluetoothDevice unbondedDevice = mock(BluetoothDevice.class);
+        when(unbondedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        CachedBluetoothDevice unbondedCachedDevice = mock(CachedBluetoothDevice.class);
+        when(unbondedCachedDevice.getDevice()).thenReturn(unbondedDevice);
+
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Arrays.asList(mBondedCachedDevice, unbondedCachedDevice));
+
+        // Make sure controller is available.
+        Shadows.shadowOf(context.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                BluetoothBondedDevicesPreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void showsOnlyBondedDevices() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(devicePreference.getCachedDevice()).isEqualTo(mBondedCachedDevice);
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_refreshesUi() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        // Unbond the only bonded device.
+        when(mBondedCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mBondedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        mController.onDeviceBondStateChanged(mBondedCachedDevice, BluetoothDevice.BOND_NONE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onDeviceClicked_notConnected_connectsToDevice() {
+        when(mBondedCachedDevice.isConnected()).thenReturn(false);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        devicePreference.performClick();
+
+        verify(mBondedCachedDevice).connect(/* connectAllProfiles= */ true);
+    }
+
+    @Test
+    public void onDeviceClicked_connected_showsDisconnectConfirmDialog() {
+        when(mBondedCachedDevice.isConnected()).thenReturn(true);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        devicePreference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(BluetoothDisconnectConfirmDialogFragment.class), isNull());
+    }
+
+    @Test
+    public void devicePreferenceButtonClicked_noUserRestrictions_launchesDetailsFragment() {
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)).thenReturn(false);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        assertThat(devicePreference.isActionShown()).isTrue();
+        devicePreference.performButtonClick();
+
+        verify(mControllerHelper.getMockFragmentController()).launchFragment(
+                any(BluetoothDeviceDetailsFragment.class));
+    }
+
+    @Test
+    public void devicePreferenceButton_disallowConfigBluetooth_actionStaysHidden() {
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)).thenReturn(true);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        assertThat(devicePreference.isActionShown()).isFalse();
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceAddressPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceAddressPreferenceControllerTest.java
new file mode 100644
index 0000000..68a901a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceAddressPreferenceControllerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link BluetoothAddressPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothDeviceAddressPreferenceControllerTest {
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void refreshUi_setsAddress() {
+        // Make sure controller is available.
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        String address = "address";
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        when(device.getAddress()).thenReturn(address);
+
+        // Construct controller.
+        Context context = RuntimeEnvironment.application;
+        Preference preference = new Preference(context);
+        PreferenceControllerTestHelper<BluetoothDeviceAddressPreferenceController>
+                controllerHelper = new PreferenceControllerTestHelper<>(context,
+                BluetoothDeviceAddressPreferenceController.class);
+        controllerHelper.getController().setCachedDevice(device);
+        controllerHelper.setPreference(preference);
+        controllerHelper.markState(Lifecycle.State.CREATED);
+
+        controllerHelper.getController().refreshUi();
+
+        assertThat(preference.getTitle()).isEqualTo(
+                context.getString(R.string.bluetooth_device_mac_address, address));
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
new file mode 100644
index 0000000..cf43354
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.widget.Button;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+/** Unit test for {@link BluetoothDeviceDetailsFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothDeviceDetailsFragmentTest {
+
+    @Mock
+    private CachedBluetoothDevice mCachedDevice;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private Context mContext;
+    private FragmentController<BluetoothDeviceDetailsFragment> mFragmentController;
+    private BluetoothDeviceDetailsFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(mContext, /* onInitCallback= */
+                null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        String address = "00:11:22:33:AA:BB";
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        when(mCachedDeviceManager.findDevice(device)).thenReturn(mCachedDevice);
+        when(mCachedDevice.getAddress()).thenReturn(address);
+
+        mFragment = BluetoothDeviceDetailsFragment.newInstance(mCachedDevice);
+        mFragmentController = FragmentController.of(mFragment);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void forgetButtonClicked_unpairsDevice() {
+        mFragmentController.setup();
+
+        findForgetButton(mFragment.requireActivity()).performClick();
+
+        verify(mCachedDevice).unpair();
+    }
+
+    @Test
+    public void forgetButtonClicked_goesBack() {
+        mFragmentController.setup();
+
+        findForgetButton(mFragment.requireActivity()).performClick();
+
+        assertThat(
+                ((BaseTestActivity) mFragment.requireActivity()).getOnBackPressedFlag()).isTrue();
+    }
+
+    @Test
+    public void connectionButtonClicked_deviceConnected_disconnectsDevice() {
+        when(mCachedDevice.isConnected()).thenReturn(true);
+        mFragmentController.setup();
+
+        findConnectionButton(mFragment.requireActivity()).performClick();
+
+        verify(mCachedDevice).disconnect();
+    }
+
+    @Test
+    public void connectionButtonClicked_deviceNotConnected_connectsDevice() {
+        when(mCachedDevice.isConnected()).thenReturn(false);
+        mFragmentController.setup();
+
+        findConnectionButton(mFragment.requireActivity()).performClick();
+
+        verify(mCachedDevice).connect(/* connectAllProfiles= */ true);
+    }
+
+    @Test
+    public void deviceConnected_connectionButtonShowsDisconnect() {
+        when(mCachedDevice.isConnected()).thenReturn(true);
+        mFragmentController.setup();
+
+        assertThat(findConnectionButton(mFragment.requireActivity()).getText()).isEqualTo(
+                mContext.getString(R.string.disconnect));
+    }
+
+    @Test
+    public void deviceNotConnected_connectionButtonShowsConnect() {
+        when(mCachedDevice.isConnected()).thenReturn(false);
+        mFragmentController.setup();
+
+        assertThat(findConnectionButton(mFragment.requireActivity()).getText()).isEqualTo(
+                mContext.getString(R.string.connect));
+    }
+
+    @Test
+    public void deviceBusy_connectionButtonDisabled() {
+        when(mCachedDevice.isBusy()).thenReturn(true);
+        mFragmentController.setup();
+
+        assertThat(findConnectionButton(mFragment.requireActivity()).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void deviceNotBusy_connectionButtonEnabled() {
+        when(mCachedDevice.isBusy()).thenReturn(false);
+        mFragmentController.setup();
+
+        assertThat(findConnectionButton(mFragment.requireActivity()).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onStart_listensForDeviceAttributesChanges() {
+        mFragmentController.create().start();
+
+        verify(mCachedDevice).registerCallback(any(CachedBluetoothDevice.Callback.class));
+    }
+
+    @Test
+    public void onStop_stopsListeningForDeviceAttributeChanges() {
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        mFragmentController.create().start();
+        verify(mCachedDevice).registerCallback(callbackCaptor.capture());
+
+        mFragmentController.stop();
+
+        verify(mCachedDevice).unregisterCallback(callbackCaptor.getValue());
+    }
+
+    @Test
+    public void deviceAttributesChanged_updatesConnectionButtonState() {
+        when(mCachedDevice.isBusy()).thenReturn(true);
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        mFragmentController.create().start();
+        assertThat(findConnectionButton(mFragment.requireActivity()).isEnabled()).isFalse();
+        verify(mCachedDevice).registerCallback(callbackCaptor.capture());
+
+        when(mCachedDevice.isBusy()).thenReturn(false);
+        callbackCaptor.getValue().onDeviceAttributesChanged();
+
+        assertThat(findConnectionButton(mFragment.requireActivity()).isEnabled()).isTrue();
+    }
+
+    private Button findForgetButton(Activity activity) {
+        return activity.findViewById(R.id.action_button2);
+    }
+
+    private Button findConnectionButton(Activity activity) {
+        return activity.findViewById(R.id.action_button1);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceNamePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceNamePreferenceControllerTest.java
new file mode 100644
index 0000000..5c4b164
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceNamePreferenceControllerTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.StringJoiner;
+
+/** Unit test for {@link BluetoothDeviceNamePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothDeviceNamePreferenceControllerTest {
+
+    @Mock
+    private CachedBluetoothDevice mDevice;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<BluetoothDeviceNamePreferenceController>
+            mControllerHelper;
+    private BluetoothDeviceNamePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */
+                null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        // Make sure controller is available.
+        Shadows.shadowOf(context.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mPreference = new Preference(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                BluetoothDeviceNamePreferenceController.class);
+        mController = mControllerHelper.getController();
+        mController.setCachedDevice(mDevice);
+        mControllerHelper.setPreference(mPreference);
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void refreshUi_setsDeviceNameAsTitle() {
+        String name = "name";
+        when(mDevice.getName()).thenReturn(name);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getTitle()).isEqualTo(name);
+    }
+
+    @Test
+    public void refreshUi_setsCarConnectionSummaryAsSummary() {
+        String summary = "summary";
+        when(mDevice.getCarConnectionSummary()).thenReturn(summary);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(summary);
+    }
+
+    @Test
+    public void refreshUi_setsIcon() {
+        when(mDevice.getBtClass()).thenReturn(
+                new BluetoothClass(BluetoothClass.Device.Major.PHONE));
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getIcon()).isNotNull();
+    }
+
+    @Test
+    public void refreshUi_hearingAidDevice_setsBatteryStatusesAsSummary() {
+        String summary = "summary";
+        when(mDevice.getCarConnectionSummary()).thenReturn(summary);
+        String otherSummary = "other summary";
+        when(mCachedDeviceManager.getHearingAidPairDeviceSummary(mDevice)).thenReturn(
+                "other summary");
+
+        mController.refreshUi();
+
+        String expected = new StringJoiner(System.lineSeparator()).add(summary).add(
+                otherSummary).toString();
+        assertThat(mPreference.getSummary()).isEqualTo(expected);
+    }
+
+    @Test
+    public void preferenceClicked_launchesRenameDialog() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mPreference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(RemoteRenameDialogFragment.class), eq(RemoteRenameDialogFragment.TAG));
+    }
+
+    @Test
+    public void preferenceClicked_handled() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        assertThat(
+                mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference)).isTrue();
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePickerFragmentTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePickerFragmentTest.java
new file mode 100644
index 0000000..d6c877b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePickerFragmentTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link BluetoothDevicePickerFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothDevicePickerFragmentTest {
+
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private FragmentController<BluetoothDevicePickerFragment> mFragmentController;
+    private BluetoothDevicePickerFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */
+                null);
+
+        mFragment = new BluetoothDevicePickerFragment();
+        mFragmentController = FragmentController.of(mFragment);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void onStart_setsBluetoothManagerForegroundActivity() {
+        mFragmentController.create().start();
+
+        assertThat(mLocalBluetoothManager.getForegroundActivity()).isEqualTo(
+                mFragment.requireActivity());
+    }
+
+    @Test
+    public void onStart_showsProgressBar() {
+        mFragmentController.create();
+        ProgressBar progressBar = findProgressBar(mFragment.requireActivity());
+        progressBar.setVisibility(View.GONE);
+
+        mFragmentController.start();
+
+        assertThat(progressBar.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onStop_clearsBluetoothManagerForegroundActivity() {
+        mFragmentController.create().start().resume().pause().stop();
+
+        assertThat(mLocalBluetoothManager.getForegroundActivity()).isNull();
+    }
+
+    @Test
+    public void onStop_hidesProgressBar() {
+        mFragmentController.setup().onPause();
+        ProgressBar progressBar = findProgressBar(mFragment.requireActivity());
+        progressBar.setVisibility(View.VISIBLE);
+
+        mFragmentController.stop();
+
+        assertThat(progressBar.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    private ProgressBar findProgressBar(Activity activity) {
+        return activity.findViewById(R.id.progress_bar);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePickerPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePickerPreferenceControllerTest.java
new file mode 100644
index 0000000..1c4934a4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePickerPreferenceControllerTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevicePicker;
+import android.bluetooth.BluetoothUuid;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelUuid;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Arrays;
+
+/** Unit test for {@link BluetoothDevicePickerPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class BluetoothDevicePickerPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private CachedBluetoothDevice mUnbondedCachedDevice;
+    @Mock
+    private BluetoothDevice mUnbondedDevice;
+    @Mock
+    private CachedBluetoothDevice mBondedCachedDevice;
+    @Mock
+    private BluetoothDevice mBondedDevice;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<BluetoothDevicePickerPreferenceController>
+            mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        Context context = RuntimeEnvironment.application;
+
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */
+                null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        when(mUnbondedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mUnbondedCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mUnbondedCachedDevice.getDevice()).thenReturn(mUnbondedDevice);
+        when(mBondedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBondedCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBondedCachedDevice.getDevice()).thenReturn(mBondedDevice);
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Arrays.asList(mUnbondedCachedDevice, mBondedCachedDevice));
+        // Make bonded device appear first in the list.
+        when(mBondedCachedDevice.compareTo(mUnbondedCachedDevice)).thenReturn(-1);
+        when(mUnbondedCachedDevice.compareTo(mBondedCachedDevice)).thenReturn(1);
+
+        // Make sure controller is available.
+        Shadows.shadowOf(context.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                BluetoothDevicePickerPreferenceController.class);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void checkInitialized_noLaunchIntentSet_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class,
+                () -> mControllerHelper.setPreference(mPreferenceGroup));
+    }
+
+    @Test
+    public void onStart_appliesFilterType() {
+        // Setup device to pass the filter.
+        when(mBondedDevice.getUuids()).thenReturn(new ParcelUuid[]{BluetoothUuid.AudioSink});
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ false,
+                BluetoothDevicePicker.FILTER_TYPE_AUDIO, "test.package", "TestClass");
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(((BluetoothDevicePreference) mPreferenceGroup.getPreference(
+                0)).getCachedDevice()).isEqualTo(mBondedCachedDevice);
+    }
+
+    @Test
+    public void onDeviceClicked_bondedDevice_sendsPickedIntent() {
+        ComponentName component = new ComponentName("test.package", "TestClass");
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ true,
+                BluetoothDevicePicker.FILTER_TYPE_ALL, component.getPackageName(),
+                component.getClassName());
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        devicePreference.performClick();
+
+        assertThat(ShadowApplication.getInstance().getBroadcastIntents()).hasSize(1);
+        Intent pickedIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(pickedIntent.getAction()).isEqualTo(
+                BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+        assertThat(pickedIntent.getComponent()).isEqualTo(component);
+        assertThat((BluetoothDevice) pickedIntent.getParcelableExtra(
+                BluetoothDevice.EXTRA_DEVICE)).isEqualTo(mBondedDevice);
+    }
+
+    @Test
+    public void onDeviceClicked_bondedDevice_goesBack() {
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ true,
+                BluetoothDevicePicker.FILTER_TYPE_ALL, "test.package", "TestClass");
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        devicePreference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).goBack();
+    }
+
+    @Test
+    public void onDeviceClicked_unbondedDevice_doesNotNeedAuth_sendsPickedIntent() {
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ false,
+                BluetoothDevicePicker.FILTER_TYPE_ALL, "test.package", "TestClass");
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(1);
+
+        devicePreference.performClick();
+
+        Intent pickedIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(pickedIntent.getAction()).isEqualTo(
+                BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+    }
+
+    @Test
+    public void onDeviceClicked_unbondedDevice_needsAuth_startsPairing() {
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ true,
+                BluetoothDevicePicker.FILTER_TYPE_ALL, "test.package", "TestClass");
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(1);
+
+        devicePreference.performClick();
+
+        verify(mUnbondedCachedDevice).startPairing();
+    }
+
+    @Test
+    public void onDeviceClicked_unbondedDevice_needsAuth_pairingStartFails_resumesScanning() {
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ true,
+                BluetoothDevicePicker.FILTER_TYPE_ALL, "test.package", "TestClass");
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(1);
+        when(mUnbondedCachedDevice.startPairing()).thenReturn(false);
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+
+        devicePreference.performClick();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_selectedDeviceBonded_sendsPickedIntent() {
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ true,
+                BluetoothDevicePicker.FILTER_TYPE_ALL, "test.package", "TestClass");
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(1);
+
+        // Select device.
+        devicePreference.performClick();
+        // Device bonds.
+        mControllerHelper.getController().onDeviceBondStateChanged(
+                devicePreference.getCachedDevice(), BluetoothDevice.BOND_BONDED);
+
+        Intent pickedIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(pickedIntent.getAction()).isEqualTo(
+                BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_selectedDeviceBonded_goesBack() {
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ true,
+                BluetoothDevicePicker.FILTER_TYPE_ALL, "test.package", "TestClass");
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(1);
+
+        // Select device.
+        devicePreference.performClick();
+        // Device bonds.
+        mControllerHelper.getController().onDeviceBondStateChanged(
+                devicePreference.getCachedDevice(), BluetoothDevice.BOND_BONDED);
+
+        verify(mControllerHelper.getMockFragmentController()).goBack();
+    }
+
+    @Test
+    public void onDestroy_noDeviceSelected_sendsNullPickedIntent() {
+        Intent launchIntent = createLaunchIntent(/* needsAuth= */ true,
+                BluetoothDevicePicker.FILTER_TYPE_ALL, "test.package", "TestClass");
+        mControllerHelper.getController().setLaunchIntent(launchIntent);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        Intent pickedIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(pickedIntent.getAction()).isEqualTo(
+                BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+        assertThat((BluetoothDevice) pickedIntent.getParcelableExtra(
+                BluetoothDevice.EXTRA_DEVICE)).isNull();
+    }
+
+    private Intent createLaunchIntent(boolean needAuth, int filterType, String packageName,
+            String className) {
+        Intent intent = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
+        intent.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, needAuth);
+        intent.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE, filterType);
+        intent.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE, packageName);
+        intent.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS, className);
+        return intent;
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceControllerTest.java
new file mode 100644
index 0000000..d79578e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceControllerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link BluetoothDevicePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class BluetoothDevicePreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private CachedBluetoothDevice mDevice;
+    private Context mContext;
+    private PreferenceControllerTestHelper<TestBluetoothDevicePreferenceController>
+            mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+
+        // Make sure controller is available.
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestBluetoothDevicePreferenceController.class);
+        mControllerHelper.getController().setCachedDevice(mDevice);
+        mControllerHelper.setPreference(new Preference(mContext));
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void setPreference_deviceNotSet_throwsIllegalStateException() {
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestBluetoothDevicePreferenceController.class);
+        assertThrows(IllegalStateException.class,
+                () -> mControllerHelper.setPreference(new Preference(mContext)));
+    }
+
+    @Test
+    public void getAvailabilityStatus_disallowConfigBluetooth_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)).thenReturn(true);
+
+        assertThat(mControllerHelper.getController().getAvailabilityStatus()).isEqualTo(
+                DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void onStart_registersDeviceCallback() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mDevice).registerCallback(any(CachedBluetoothDevice.Callback.class));
+    }
+
+    @Test
+    public void onStop_unregistersDeviceCallback() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        verify(mDevice).registerCallback(callbackCaptor.capture());
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mDevice).unregisterCallback(callbackCaptor.getValue());
+    }
+
+    @Test
+    public void started_onDeviceAttributesChanged_refreshesUi() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        verify(mDevice).registerCallback(callbackCaptor.capture());
+        // onCreate, onStart.
+        assertThat(mControllerHelper.getController().getUpdateStateCallCount()).isEqualTo(2);
+
+        callbackCaptor.getValue().onDeviceAttributesChanged();
+
+        // onCreate, onStart, callback.
+        assertThat(mControllerHelper.getController().getUpdateStateCallCount()).isEqualTo(3);
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+
+    /** Concrete impl of {@link BluetoothDevicePreferenceController} for testing. */
+    private static class TestBluetoothDevicePreferenceController extends
+            BluetoothDevicePreferenceController<Preference> {
+
+        private int mUpdateStateCallCount;
+
+        TestBluetoothDevicePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected Class<Preference> getPreferenceType() {
+            return Preference.class;
+        }
+
+        @Override
+        protected void updateState(Preference preference) {
+            mUpdateStateCallCount++;
+        }
+
+        int getUpdateStateCallCount() {
+            return mUpdateStateCallCount;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceTest.java
new file mode 100644
index 0000000..b3003f6
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothClass;
+import android.content.Context;
+import android.os.SystemProperties;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link BluetoothDevicePreference}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class BluetoothDevicePreferenceTest {
+
+    @Mock
+    private CachedBluetoothDevice mCachedDevice;
+    private Context mContext;
+    private BluetoothDevicePreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new BluetoothDevicePreference(mContext, mCachedDevice);
+    }
+
+    @Test
+    public void actionIsHiddenByDefault() {
+        assertThat(mPreference.isActionShown()).isFalse();
+    }
+
+    @Test
+    public void onAttached_registersDeviceCallback() {
+        mPreference.onAttached();
+
+        verify(mCachedDevice).registerCallback(any(CachedBluetoothDevice.Callback.class));
+    }
+
+    @Test
+    public void onAttached_setsDeviceNameAsTitle() {
+        String name = "name";
+        when(mCachedDevice.getName()).thenReturn(name);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.getTitle()).isEqualTo(name);
+    }
+
+    @Test
+    public void onAttached_setsCarConnectionSummaryAsSummary() {
+        String summary = "summary";
+        when(mCachedDevice.getCarConnectionSummary()).thenReturn(summary);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.getSummary()).isEqualTo(summary);
+    }
+
+    @Test
+    public void onAttached_setsIcon() {
+        when(mCachedDevice.getBtClass()).thenReturn(
+                new BluetoothClass(BluetoothClass.Device.Major.PHONE));
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.getIcon()).isNotNull();
+    }
+
+    @Test
+    public void onAttached_deviceNotBusy_setsEnabled() {
+        when(mCachedDevice.isBusy()).thenReturn(false);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onAttached_deviceBusy_setsNotEnabled() {
+        when(mCachedDevice.isBusy()).thenReturn(true);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onAttached_deviceNameNotHumanReadable_setsHidden() {
+        when(mCachedDevice.hasHumanReadableName()).thenReturn(false);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void onAttached_deviceNameNotHumanReadable_showWithoutNamesTrue_setsShown() {
+        SystemProperties.set("persist.bluetooth.showdeviceswithoutnames", Boolean.TRUE.toString());
+        when(mCachedDevice.hasHumanReadableName()).thenReturn(false);
+        mPreference = new BluetoothDevicePreference(mContext, mCachedDevice);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void onDetached_unregistersDeviceCallback() {
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        mPreference.onAttached();
+        verify(mCachedDevice).registerCallback(callbackCaptor.capture());
+
+        mPreference.onDetached();
+
+        verify(mCachedDevice).unregisterCallback(callbackCaptor.getValue());
+    }
+
+    @Test
+    public void onDeviceAttributesChanged_refreshesUi() {
+        String name = "name";
+        when(mCachedDevice.getName()).thenReturn(name);
+        String summary = "summary";
+        when(mCachedDevice.getCarConnectionSummary()).thenReturn(summary);
+        when(mCachedDevice.isBusy()).thenReturn(false);
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        mPreference.onAttached();
+        verify(mCachedDevice).registerCallback(callbackCaptor.capture());
+
+        assertThat(mPreference.getTitle()).isEqualTo(name);
+        assertThat(mPreference.getSummary()).isEqualTo(summary);
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        String updatedName = "updatedName";
+        when(mCachedDevice.getName()).thenReturn(updatedName);
+        String updatedSummary = "updatedSummary";
+        when(mCachedDevice.getCarConnectionSummary()).thenReturn(updatedSummary);
+        when(mCachedDevice.isBusy()).thenReturn(true);
+
+        callbackCaptor.getValue().onDeviceAttributesChanged();
+
+        assertThat(mPreference.getTitle()).isEqualTo(updatedName);
+        assertThat(mPreference.getSummary()).isEqualTo(updatedSummary);
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void equals_devicesEqual_returnsTrue() {
+        BluetoothDevicePreference otherPreference = new BluetoothDevicePreference(mContext,
+                mCachedDevice);
+
+        assertThat(mPreference.equals(otherPreference)).isTrue();
+    }
+
+    @Test
+    public void equals_devicesNotEqual_returnsFalse() {
+        BluetoothDevicePreference otherPreference = new BluetoothDevicePreference(mContext,
+                mock(CachedBluetoothDevice.class));
+
+        assertThat(mPreference.equals(otherPreference)).isFalse();
+    }
+
+    @Test
+    public void compareTo_sameType_usesDeviceCompareTo() {
+        CachedBluetoothDevice otherDevice = mock(CachedBluetoothDevice.class);
+        BluetoothDevicePreference otherPreference = new BluetoothDevicePreference(mContext,
+                otherDevice);
+        when(mCachedDevice.compareTo(otherDevice)).thenReturn(1);
+        when(otherDevice.compareTo(mCachedDevice)).thenReturn(-1);
+
+        assertThat(mPreference.compareTo(otherPreference)).isEqualTo(1);
+        assertThat(otherPreference.compareTo(mPreference)).isEqualTo(-1);
+    }
+
+    @Test
+    public void compareTo_differentType_fallsBackToDefaultCompare() {
+        mPreference.setOrder(1);
+        Preference otherPreference = new Preference(mContext);
+        otherPreference.setOrder(2);
+
+        assertThat(mPreference.compareTo(otherPreference)).isEqualTo(-1);
+        verify(mCachedDevice, never()).compareTo(any());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilePreferenceTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilePreferenceTest.java
new file mode 100644
index 0000000..5d04587
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilePreferenceTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+import com.android.settingslib.bluetooth.PanProfile;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class BluetoothDeviceProfilePreferenceTest {
+
+    @Mock
+    private LocalBluetoothProfile mProfile;
+    @Mock
+    private CachedBluetoothDevice mCachedDevice;
+    private BluetoothDevice mDevice;
+    private Context mContext;
+    private BluetoothDeviceProfilePreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:AA:BB");
+        when(mCachedDevice.getDevice()).thenReturn(mDevice);
+        when(mProfile.toString()).thenReturn("key");
+        when(mProfile.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        mPreference = new BluetoothDeviceProfilePreference(mContext, mProfile, mCachedDevice);
+    }
+
+    @Test
+    public void onConstruction_setsProfileStringAsKey() {
+        assertThat(mPreference.getKey()).isEqualTo(mProfile.toString());
+    }
+
+    @Test
+    public void onConstruction_setsProfileNameAsTitle() {
+        assertThat(mPreference.getTitle()).isEqualTo(mContext.getString(R.string.bt_profile_name));
+    }
+
+    @Test
+    public void onAttached_registersDeviceCallback() {
+        mPreference.onAttached();
+
+        verify(mCachedDevice).registerCallback(any(CachedBluetoothDevice.Callback.class));
+    }
+
+    @Test
+    public void onAttached_deviceNotBusy_setsEnabled() {
+        when(mCachedDevice.isBusy()).thenReturn(false);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onAttached_deviceBusy_setsNotEnabled() {
+        when(mCachedDevice.isBusy()).thenReturn(true);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onAttached_preferred_setsChecked() {
+        when(mProfile.isPreferred(mDevice)).thenReturn(true);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onAttached_notPreferred_setsUnchecked() {
+        when(mProfile.isPreferred(mDevice)).thenReturn(false);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onAttached_panProfile_connected_setsChecked() {
+        mProfile = mock(PanProfile.class);
+        when(mProfile.getConnectionStatus(mDevice)).thenReturn(STATE_CONNECTED);
+        when(mProfile.toString()).thenReturn("key");
+        when(mProfile.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        mPreference = new BluetoothDeviceProfilePreference(mContext, mProfile, mCachedDevice);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onAttached_panProfile_notConnected_setsUnchecked() {
+        mProfile = mock(PanProfile.class);
+        when(mProfile.getConnectionStatus(mDevice)).thenReturn(STATE_DISCONNECTED);
+        when(mProfile.toString()).thenReturn("key");
+        when(mProfile.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        mPreference = new BluetoothDeviceProfilePreference(mContext, mProfile, mCachedDevice);
+
+        mPreference.onAttached();
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onDeviceAttributesChanged_refreshesUi() {
+        when(mProfile.isPreferred(mDevice)).thenReturn(false);
+        when(mCachedDevice.isBusy()).thenReturn(false);
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        mPreference.onAttached();
+        verify(mCachedDevice).registerCallback(callbackCaptor.capture());
+
+        assertThat(mPreference.isEnabled()).isTrue();
+        assertThat(mPreference.isChecked()).isFalse();
+
+        when(mProfile.isPreferred(mDevice)).thenReturn(true);
+        when(mCachedDevice.isBusy()).thenReturn(true);
+
+        callbackCaptor.getValue().onDeviceAttributesChanged();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onDetached_unregistersDeviceCallback() {
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        mPreference.onAttached();
+        verify(mCachedDevice).registerCallback(callbackCaptor.capture());
+
+        mPreference.onDetached();
+
+        verify(mCachedDevice).unregisterCallback(callbackCaptor.getValue());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilesPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilesPreferenceControllerTest.java
new file mode 100644
index 0000000..31ecd21
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDeviceProfilesPreferenceControllerTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/** Unit test for {@link BluetoothDeviceProfilesPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothDeviceProfilesPreferenceControllerTest {
+
+    @Mock
+    private CachedBluetoothDevice mCachedDevice;
+    private BluetoothDevice mDevice;
+    private PreferenceGroup mPreferenceGroup;
+    private BluetoothDeviceProfilesPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:AA:BB");
+        when(mCachedDevice.getDevice()).thenReturn(mDevice);
+
+        // Make sure controller is available.
+        Shadows.shadowOf(context.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mPreferenceGroup = new PreferenceCategory(context);
+        PreferenceControllerTestHelper<BluetoothDeviceProfilesPreferenceController>
+                controllerHelper = new PreferenceControllerTestHelper<>(context,
+                BluetoothDeviceProfilesPreferenceController.class);
+        mController = controllerHelper.getController();
+        mController.setCachedDevice(mCachedDevice);
+        controllerHelper.setPreference(mPreferenceGroup);
+        controllerHelper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void refreshUi_addsNewProfiles() {
+        LocalBluetoothProfile profile1 = mock(LocalBluetoothProfile.class);
+        when(profile1.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        when(mCachedDevice.getProfiles()).thenReturn(Collections.singletonList(profile1));
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        LocalBluetoothProfile profile2 = mock(LocalBluetoothProfile.class);
+        when(profile2.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        when(mCachedDevice.getProfiles()).thenReturn(Arrays.asList(profile1, profile2));
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+        BluetoothDeviceProfilePreference profilePreference =
+                (BluetoothDeviceProfilePreference) mPreferenceGroup.getPreference(1);
+        assertThat(profilePreference.getProfile()).isEqualTo(profile2);
+    }
+
+    @Test
+    public void refreshUi_removesRemovedProfiles() {
+        LocalBluetoothProfile profile1 = mock(LocalBluetoothProfile.class);
+        when(profile1.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        LocalBluetoothProfile profile2 = mock(LocalBluetoothProfile.class);
+        when(profile2.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        when(mCachedDevice.getProfiles()).thenReturn(Arrays.asList(profile1, profile2));
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        when(mCachedDevice.getProfiles()).thenReturn(Collections.singletonList(profile2));
+        when(mCachedDevice.getRemovedProfiles()).thenReturn(Collections.singletonList(profile1));
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        BluetoothDeviceProfilePreference profilePreference =
+                (BluetoothDeviceProfilePreference) mPreferenceGroup.getPreference(0);
+        assertThat(profilePreference.getProfile()).isEqualTo(profile2);
+    }
+
+    @Test
+    public void refreshUi_profiles_showsPreference() {
+        LocalBluetoothProfile profile = mock(LocalBluetoothProfile.class);
+        when(profile.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        when(mCachedDevice.getProfiles()).thenReturn(Collections.singletonList(profile));
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_noProfiles_hidesPreference() {
+        when(mCachedDevice.getProfiles()).thenReturn(Collections.emptyList());
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isFalse();
+    }
+
+    @Test
+    public void profileChecked_setsProfilePreferred() {
+        LocalBluetoothProfile profile = mock(LocalBluetoothProfile.class);
+        when(profile.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        when(mCachedDevice.getProfiles()).thenReturn(Collections.singletonList(profile));
+        mController.refreshUi();
+        BluetoothDeviceProfilePreference profilePreference =
+                (BluetoothDeviceProfilePreference) mPreferenceGroup.getPreference(0);
+
+        assertThat(profilePreference.isChecked()).isFalse();
+        profilePreference.performClick();
+
+        verify(profile).setPreferred(mDevice, true);
+    }
+
+    @Test
+    public void profileChecked_connectsToProfile() {
+        LocalBluetoothProfile profile = mock(LocalBluetoothProfile.class);
+        when(profile.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        when(mCachedDevice.getProfiles()).thenReturn(Collections.singletonList(profile));
+        mController.refreshUi();
+        BluetoothDeviceProfilePreference profilePreference =
+                (BluetoothDeviceProfilePreference) mPreferenceGroup.getPreference(0);
+
+        assertThat(profilePreference.isChecked()).isFalse();
+        profilePreference.performClick();
+
+        verify(mCachedDevice).connectProfile(profile);
+    }
+
+    @Test
+    public void profileUnchecked_setsProfileNotPreferred() {
+        LocalBluetoothProfile profile = mock(LocalBluetoothProfile.class);
+        when(profile.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        when(profile.isPreferred(mDevice)).thenReturn(true);
+        when(mCachedDevice.getProfiles()).thenReturn(Collections.singletonList(profile));
+        mController.refreshUi();
+        BluetoothDeviceProfilePreference profilePreference =
+                (BluetoothDeviceProfilePreference) mPreferenceGroup.getPreference(0);
+
+        assertThat(profilePreference.isChecked()).isTrue();
+        profilePreference.performClick();
+
+        verify(profile).setPreferred(mDevice, false);
+    }
+
+    @Test
+    public void profileUnchecked_disconnectsFromProfile() {
+        LocalBluetoothProfile profile = mock(LocalBluetoothProfile.class);
+        when(profile.getNameResource(mDevice)).thenReturn(R.string.bt_profile_name);
+        when(profile.isPreferred(mDevice)).thenReturn(true);
+        when(mCachedDevice.getProfiles()).thenReturn(Collections.singletonList(profile));
+        mController.refreshUi();
+        BluetoothDeviceProfilePreference profilePreference =
+                (BluetoothDeviceProfilePreference) mPreferenceGroup.getPreference(0);
+
+        assertThat(profilePreference.isChecked()).isTrue();
+        profilePreference.performClick();
+
+        verify(mCachedDevice).disconnect(profile);
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicesGroupPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicesGroupPreferenceControllerTest.java
new file mode 100644
index 0000000..da7406f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDevicesGroupPreferenceControllerTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/** Unit test for {@link BluetoothDevicesGroupPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothDevicesGroupPreferenceControllerTest {
+
+    @Mock
+    private BluetoothDeviceFilter.Filter mFilter;
+    @Mock
+    private CachedBluetoothDevice mCachedDevice1;
+    @Mock
+    private CachedBluetoothDevice mCachedDevice2;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private BluetoothDevice mDevice1;
+    private PreferenceGroup mPreferenceGroup;
+    private TestBluetoothDevicesGroupPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */
+                null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        mDevice1 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:AA:BB");
+        when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
+        BluetoothDevice device2 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+                "BB:AA:33:22:11:00");
+        when(mCachedDevice2.getDevice()).thenReturn(device2);
+
+        // Make sure controller is available.
+        Shadows.shadowOf(context.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mPreferenceGroup = new PreferenceCategory(context);
+        PreferenceControllerTestHelper<TestBluetoothDevicesGroupPreferenceController>
+                controllerHelper = new PreferenceControllerTestHelper<>(context,
+                TestBluetoothDevicesGroupPreferenceController.class);
+        mController = controllerHelper.getController();
+        mController.setDeviceFilter(mFilter);
+        controllerHelper.setPreference(mPreferenceGroup);
+        controllerHelper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void refreshUi_filterMatch_addsToGroup() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        when(mFilter.matches(mDevice1)).thenReturn(true);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(devicePreference.getCachedDevice()).isEqualTo(mCachedDevice1);
+    }
+
+    @Test
+    public void refreshUi_filterMatch_addsToPreferenceMap() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        when(mFilter.matches(mDevice1)).thenReturn(true);
+
+        mController.refreshUi();
+
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(mController.getPreferenceMap()).containsEntry(devicePreference.getCachedDevice(),
+                devicePreference);
+    }
+
+    @Test
+    public void refreshUi_filterMismatch_removesFromGroup() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        when(mFilter.matches(mDevice1)).thenReturn(true);
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(devicePreference.getCachedDevice()).isEqualTo(mCachedDevice1);
+
+        when(mFilter.matches(mDevice1)).thenReturn(false);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_filterMismatch_removesFromPreferenceMap() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        when(mFilter.matches(mDevice1)).thenReturn(true);
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(devicePreference.getCachedDevice()).isEqualTo(mCachedDevice1);
+
+        when(mFilter.matches(mDevice1)).thenReturn(false);
+        mController.refreshUi();
+
+        assertThat(mController.getPreferenceMap()).doesNotContainKey(mCachedDevice1);
+    }
+
+    @Test
+    public void refreshUi_noDevices_hidesGroup() {
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_devices_showsGroup() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        when(mFilter.matches(mDevice1)).thenReturn(true);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isTrue();
+    }
+
+    @Test
+    public void onBluetoothStateChanged_turningOff_clearsPreferences() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        when(mFilter.matches(mDevice1)).thenReturn(true);
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+
+        mController.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_OFF);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+        assertThat(mController.getPreferenceMap()).isEmpty();
+    }
+
+    @Test
+    public void onDeviceAdded_refreshesUi() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        when(mFilter.matches(mDevice1)).thenReturn(true);
+
+        mController.onDeviceAdded(mCachedDevice1);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(devicePreference.getCachedDevice()).isEqualTo(mCachedDevice1);
+    }
+
+    @Test
+    public void onDeviceDeleted_refreshesUi() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Arrays.asList(mCachedDevice1, mCachedDevice2));
+        when(mFilter.matches(any(BluetoothDevice.class))).thenReturn(true);
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice2));
+        mController.onDeviceDeleted(mCachedDevice1);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(devicePreference.getCachedDevice()).isEqualTo(mCachedDevice2);
+    }
+
+    @Test
+    public void onDeviceDeleted_lastDevice_hidesGroup() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice1));
+        when(mFilter.matches(any(BluetoothDevice.class))).thenReturn(true);
+        mController.refreshUi();
+
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(Collections.emptyList());
+        mController.onDeviceDeleted(mCachedDevice1);
+
+        assertThat(mPreferenceGroup.isVisible()).isFalse();
+    }
+
+    @Test
+    public void preferenceClicked_callsOnDeviceClicked() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Arrays.asList(mCachedDevice1, mCachedDevice2));
+        when(mFilter.matches(any(BluetoothDevice.class))).thenReturn(true);
+        mController.refreshUi();
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(1);
+
+        devicePreference.performClick();
+
+        assertThat(mController.getClickedDevice()).isEqualTo(devicePreference.getCachedDevice());
+    }
+
+    @Test
+    public void preferenceClicked_handled() {
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Arrays.asList(mCachedDevice1, mCachedDevice2));
+        when(mFilter.matches(any(BluetoothDevice.class))).thenReturn(true);
+        mController.refreshUi();
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(1);
+
+        assertThat(devicePreference.getOnPreferenceClickListener().onPreferenceClick(
+                devicePreference)).isTrue();
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+
+    /** Concrete impl of {@link BluetoothDevicesGroupPreferenceController} for testing. */
+    private static class TestBluetoothDevicesGroupPreferenceController extends
+            BluetoothDevicesGroupPreferenceController {
+
+        private BluetoothDeviceFilter.Filter mFilter;
+        private CachedBluetoothDevice mClickedDevice;
+
+        TestBluetoothDevicesGroupPreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected BluetoothDeviceFilter.Filter getDeviceFilter() {
+            return mFilter;
+        }
+
+        void setDeviceFilter(BluetoothDeviceFilter.Filter filter) {
+            mFilter = filter;
+        }
+
+        @Override
+        protected void onDeviceClicked(CachedBluetoothDevice cachedDevice) {
+            mClickedDevice = cachedDevice;
+        }
+
+        CachedBluetoothDevice getClickedDevice() {
+            return mClickedDevice;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDisconnectConfirmDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDisconnectConfirmDialogFragmentTest.java
new file mode 100644
index 0000000..1653461
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothDisconnectConfirmDialogFragmentTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.DialogInterface;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowDialog;
+import org.robolectric.util.ReflectionHelpers;
+
+/** Unit test for {@link BluetoothDisconnectConfirmDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothDisconnectConfirmDialogFragmentTest {
+
+    @Mock
+    private CachedBluetoothDevice mCachedDevice;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private BluetoothDisconnectConfirmDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(
+                RuntimeEnvironment.application, /* onInitCallback= */ null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        String address = "00:11:22:33:AA:BB";
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        when(mCachedDeviceManager.findDevice(device)).thenReturn(mCachedDevice);
+        when(mCachedDevice.getAddress()).thenReturn(address);
+        when(mCachedDevice.isConnected()).thenReturn(true);
+
+        mFragment = BluetoothDisconnectConfirmDialogFragment.newInstance(mCachedDevice);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void confirmDisconnect_disconnectsFromDevice() {
+        AlertDialog dialog = showDialog(mFragment);
+
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        verify(mCachedDevice).disconnect();
+    }
+
+    @Test
+    public void deviceNoLongerConnected_dismiss() {
+        ArgumentCaptor<CachedBluetoothDevice.Callback> callbackCaptor = ArgumentCaptor.forClass(
+                CachedBluetoothDevice.Callback.class);
+        AlertDialog dialog = showDialog(mFragment);
+        verify(mCachedDevice).registerCallback(callbackCaptor.capture());
+
+        when(mCachedDevice.isConnected()).thenReturn(false);
+        callbackCaptor.getValue().onDeviceAttributesChanged();
+
+        assertThat(dialog.isShowing()).isFalse();
+    }
+
+    private AlertDialog showDialog(BluetoothDisconnectConfirmDialogFragment fragment) {
+        BaseTestActivity activity = Robolectric.setupActivity(BaseTestActivity.class);
+        activity.showDialog(fragment, /* tag= */ null);
+        return (AlertDialog) ShadowDialog.getLatestDialog();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..64647f5
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothEntryPreferenceControllerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_BLUETOOTH;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link BluetoothEntryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class BluetoothEntryPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private BluetoothEntryPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mController = new PreferenceControllerTestHelper<>(RuntimeEnvironment.application,
+                BluetoothEntryPreferenceController.class).getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_bluetoothAvailable_available() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(any())).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_bluetoothAvailable_disallowBluetooth_disabledForUser() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_BLUETOOTH)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_bluetoothNotAvailable_unsupportedOnDevice() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothNamePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothNamePreferenceControllerTest.java
new file mode 100644
index 0000000..353c7a1
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothNamePreferenceControllerTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link BluetoothNamePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class BluetoothNamePreferenceControllerTest {
+
+    private static final String NAME = "name";
+    private static final String NAME_UPDATED = "name updated";
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<BluetoothNamePreferenceController> mControllerHelper;
+    private BluetoothNamePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        Context context = RuntimeEnvironment.application;
+
+        // Make sure controller is available.
+        Shadows.shadowOf(context.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mPreference = new Preference(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                BluetoothNamePreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void refreshUi_setsNameAsSummary() {
+        BluetoothAdapter.getDefaultAdapter().setName(NAME);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(NAME);
+    }
+
+    @Test
+    public void refreshUi_noUserRestrictions_setsSelectable() {
+        mController.refreshUi();
+
+        assertThat(mPreference.isSelectable()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_userHasConfigRestriction_setsNotSelectable() {
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)).thenReturn(true);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isSelectable()).isFalse();
+    }
+
+    @Test
+    public void started_localNameChangedBroadcast_updatesSummary() {
+        BluetoothAdapter.getDefaultAdapter().setName(NAME);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreference.getSummary()).isEqualTo(NAME);
+
+        BluetoothAdapter.getDefaultAdapter().setName(NAME_UPDATED);
+        RuntimeEnvironment.application.sendBroadcast(
+                new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED));
+
+        assertThat(mPreference.getSummary()).isEqualTo(NAME_UPDATED);
+    }
+
+    @Test
+    public void stopped_noUpdateOnLocalNameChangedBroadcast() {
+        BluetoothAdapter.getDefaultAdapter().setName(NAME);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreference.getSummary()).isEqualTo(NAME);
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        BluetoothAdapter.getDefaultAdapter().setName(NAME_UPDATED);
+        RuntimeEnvironment.application.sendBroadcast(
+                new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED));
+
+        assertThat(mPreference.getSummary()).isEqualTo(NAME);
+    }
+
+    @Test
+    public void preferenceClicked_launchesRenameDialog() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mPreference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(LocalRenameDialogFragment.class), eq(LocalRenameDialogFragment.TAG));
+    }
+
+    @Test
+    public void preferenceClicked_handled() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        assertThat(
+                mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference)).isTrue();
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothPairingSelectionFragmentTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothPairingSelectionFragmentTest.java
new file mode 100644
index 0000000..2073778
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothPairingSelectionFragmentTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+/** Unit test for {@link BluetoothPairingSelectionFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothPairingSelectionFragmentTest {
+
+    @Mock
+    private BluetoothEventManager mEventManager;
+    private BluetoothEventManager mSaveRealEventManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private FragmentController<BluetoothPairingSelectionFragment> mFragmentController;
+    private BluetoothPairingSelectionFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */
+                null);
+        mSaveRealEventManager = mLocalBluetoothManager.getEventManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mEventManager", mEventManager);
+
+        mFragment = new BluetoothPairingSelectionFragment();
+        mFragmentController = FragmentController.of(mFragment);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mEventManager", mSaveRealEventManager);
+    }
+
+    @Test
+    public void onStart_setsBluetoothManagerForegroundActivity() {
+        mFragmentController.create().start();
+
+        assertThat(mLocalBluetoothManager.getForegroundActivity()).isEqualTo(
+                mFragment.requireActivity());
+    }
+
+    @Test
+    public void onStart_registersEventListener() {
+        mFragmentController.create().start();
+
+        verify(mEventManager).registerCallback(any(BluetoothCallback.class));
+    }
+
+    @Test
+    public void onStart_showsProgressBar() {
+        mFragmentController.create();
+        ProgressBar progressBar = findProgressBar(mFragment.requireActivity());
+        progressBar.setVisibility(View.GONE);
+
+        mFragmentController.start();
+
+        assertThat(progressBar.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onStop_clearsBluetoothManagerForegroundActivity() {
+        mFragmentController.create().start().resume().pause().stop();
+
+        assertThat(mLocalBluetoothManager.getForegroundActivity()).isNull();
+    }
+
+    @Test
+    public void onStop_unregistersEventListener() {
+        ArgumentCaptor<BluetoothCallback> callbackCaptor = ArgumentCaptor.forClass(
+                BluetoothCallback.class);
+        mFragmentController.create().start().resume().pause().stop();
+
+        verify(mEventManager).registerCallback(callbackCaptor.capture());
+        verify(mEventManager).unregisterCallback(callbackCaptor.getValue());
+    }
+
+    @Test
+    public void onStop_hidesProgressBar() {
+        mFragmentController.setup().onPause();
+        ProgressBar progressBar = findProgressBar(mFragment.requireActivity());
+        progressBar.setVisibility(View.VISIBLE);
+
+        mFragmentController.stop();
+
+        assertThat(progressBar.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_deviceBonded_goesBack() {
+        ArgumentCaptor<BluetoothCallback> callbackCaptor = ArgumentCaptor.forClass(
+                BluetoothCallback.class);
+        mFragmentController.setup();
+        verify(mEventManager).registerCallback(callbackCaptor.capture());
+
+        callbackCaptor.getValue().onDeviceBondStateChanged(mock(CachedBluetoothDevice.class),
+                BluetoothDevice.BOND_BONDED);
+
+        assertThat(
+                ((BaseTestActivity) mFragment.requireActivity()).getOnBackPressedFlag()).isTrue();
+    }
+
+    private ProgressBar findProgressBar(Activity activity) {
+        return activity.findViewById(R.id.progress_bar);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothPreferenceControllerTest.java
new file mode 100644
index 0000000..c58f6b4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothPreferenceControllerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_BLUETOOTH;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.CONDITIONALLY_UNAVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+
+/** Unit test for {@link BluetoothPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class BluetoothPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private BluetoothEventManager mEventManager;
+    private BluetoothEventManager mSaveRealEventManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private PreferenceControllerTestHelper<TestBluetoothPreferenceController> mControllerHelper;
+    private TestBluetoothPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        Context context = RuntimeEnvironment.application;
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */
+                null);
+        mSaveRealEventManager = mLocalBluetoothManager.getEventManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mEventManager", mEventManager);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                TestBluetoothPreferenceController.class, new Preference(context));
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mEventManager", mSaveRealEventManager);
+    }
+
+    @Test
+    public void getAvailabilityStatus_bluetoothNotAvailable_unsupportedOnDevice() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_disallowBluetoothUserRestriction_disabledForUser() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_BLUETOOTH)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_adapterDisabled_conditionallyUnavailable() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().disable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_OFF);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_available() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+        // No user restrictions.
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void onStart_registersEventListener() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mEventManager).registerCallback(mController);
+    }
+
+    @Test
+    public void onStop_unregistersEventListener() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mEventManager).unregisterCallback(mController);
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+
+    /** Concrete impl of {@link BluetoothPreferenceController} for testing. */
+    private static class TestBluetoothPreferenceController extends
+            BluetoothPreferenceController<Preference> {
+
+        TestBluetoothPreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected Class<Preference> getPreferenceType() {
+            return Preference.class;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothRenameDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothRenameDialogFragmentTest.java
new file mode 100644
index 0000000..e7b8bc2
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothRenameDialogFragmentTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowDialog;
+
+/** Unit test for {@link BluetoothRenameDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class BluetoothRenameDialogFragmentTest {
+
+    private TestBluetoothRenameDialogFragment mFragment;
+    private AlertDialog mDialog;
+
+    @Before
+    public void setUp() {
+        BaseTestActivity activity = Robolectric.setupActivity(BaseTestActivity.class);
+        mFragment = new TestBluetoothRenameDialogFragment();
+        activity.showDialog(mFragment, /* tag= */ null);
+        mDialog = (AlertDialog) ShadowDialog.getLatestDialog();
+    }
+
+    @Test
+    public void initialTextIsCurrentDeviceName() {
+        EditText editText = mDialog.findViewById(android.R.id.edit);
+
+        assertThat(editText.getText().toString()).isEqualTo(mFragment.getDeviceName());
+    }
+
+    @Test
+    public void softInputShown() {
+        InputMethodManager imm =
+                (InputMethodManager) RuntimeEnvironment.application.getSystemService(
+                        Context.INPUT_METHOD_SERVICE);
+        assertThat(Shadows.shadowOf(imm).isSoftInputVisible()).isTrue();
+    }
+
+    @Test
+    public void noUserInput_positiveButtonDisabled() {
+        assertThat(mDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void userInput_positiveButtonEnabled() {
+        EditText editText = mDialog.findViewById(android.R.id.edit);
+        editText.append("1234");
+
+        assertThat(mDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void userInput_emptyName_positiveButtonDisabled() {
+        EditText editText = mDialog.findViewById(android.R.id.edit);
+        editText.setText("");
+
+        assertThat(mDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void nameUpdatedByCode_positiveButtonDisabled() {
+        EditText editText = mDialog.findViewById(android.R.id.edit);
+        editText.append("1234");
+
+        mFragment.updateDeviceName();
+
+        assertThat(mDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void editorDoneAction_dismissesDialog() {
+        EditText editText = mDialog.findViewById(android.R.id.edit);
+
+        editText.onEditorAction(EditorInfo.IME_ACTION_DONE);
+
+        assertThat(mDialog.isShowing()).isFalse();
+    }
+
+    @Test
+    public void editorDoneAction_setsDeviceName() {
+        EditText editText = mDialog.findViewById(android.R.id.edit);
+        String editStr = "1234";
+        String expectedName = mFragment.getDeviceName() + editStr;
+
+        editText.append(editStr);
+        editText.onEditorAction(EditorInfo.IME_ACTION_DONE);
+
+        assertThat(mFragment.getDeviceName()).isEqualTo(expectedName);
+    }
+
+    @Test
+    public void editorDoneAction_emptyName_doesNotSetDeviceName() {
+        EditText editText = mDialog.findViewById(android.R.id.edit);
+        String expectedName = mFragment.getDeviceName();
+        String editStr = "";
+
+        editText.setText(editStr);
+        editText.onEditorAction(EditorInfo.IME_ACTION_DONE);
+
+        assertThat(mFragment.getDeviceName()).isEqualTo(expectedName);
+    }
+
+    @Test
+    public void positiveButtonClicked_setsDeviceName() {
+        EditText editText = mDialog.findViewById(android.R.id.edit);
+        String editStr = "1234";
+        String expectedName = mFragment.getDeviceName() + editStr;
+
+        editText.append(editStr);
+        mDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
+
+        assertThat(mFragment.getDeviceName()).isEqualTo(expectedName);
+    }
+
+    /** Concrete impl of {@link BluetoothRenameDialogFragment} for testing. */
+    public static class TestBluetoothRenameDialogFragment extends BluetoothRenameDialogFragment {
+
+        private String mSetDeviceNameArg = "Device Name";
+
+        @Override
+        @StringRes
+        protected int getDialogTitle() {
+            return R.string.bt_rename_dialog_title;
+        }
+
+        @Nullable
+        @Override
+        protected String getDeviceName() {
+            return mSetDeviceNameArg;
+        }
+
+        @Override
+        protected void setDeviceName(String deviceName) {
+            mSetDeviceNameArg = deviceName;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothScanningDevicesGroupPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothScanningDevicesGroupPreferenceControllerTest.java
new file mode 100644
index 0000000..3f62452
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothScanningDevicesGroupPreferenceControllerTest.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Collections;
+
+/** Unit test for {@link BluetoothScanningDevicesGroupPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class BluetoothScanningDevicesGroupPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private CachedBluetoothDevice mCachedDevice;
+    @Mock
+    private BluetoothDevice mDevice;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<TestBluetoothScanningDevicesGroupPreferenceController>
+            mControllerHelper;
+    private TestBluetoothScanningDevicesGroupPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(mContext, /* onInitCallback= */
+                null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mCachedDevice.getDevice()).thenReturn(mDevice);
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Collections.singletonList(mCachedDevice));
+
+        // Make sure controller is available.
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestBluetoothScanningDevicesGroupPreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void disallowConfigBluetooth_doesNotStartScanning() {
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)).thenReturn(true);
+
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isFalse();
+        // User can't scan, but they can still see known devices.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onScanningStateChanged_scanningEnabled_receiveStopped_restartsScanning() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+
+        BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
+        mController.onScanningStateChanged(/* started= */ false);
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+    }
+
+    @Test
+    public void onScanningStateChanged_scanningDisabled_receiveStopped_doesNothing() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        // Set a device bonding to disable scanning.
+        when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+        mController.refreshUi();
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isFalse();
+
+        mController.onScanningStateChanged(/* started= */ false);
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isFalse();
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_refreshesUi() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+
+        // Change state to bonding to cancel scanning on refresh.
+        when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+        mController.onDeviceBondStateChanged(mCachedDevice, BluetoothDevice.BOND_BONDING);
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isFalse();
+    }
+
+    @Test
+    public void onDeviceClicked_callsInternal() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        devicePreference.performClick();
+
+        assertThat(mController.getLastClickedDevice()).isEquivalentAccordingToCompareTo(
+                devicePreference.getCachedDevice());
+    }
+
+    @Test
+    public void onDeviceClicked_cancelsScanning() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+
+        devicePreference.performClick();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_noDeviceBonding_startsScanning() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mController.refreshUi();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_noDeviceBonding_enablesGroup() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_noDeviceBonding_setsScanModeConnectableDiscoverable() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mController.refreshUi();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().getScanMode()).isEqualTo(
+                BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+    }
+
+    @Test
+    public void refreshUi_deviceBonding_stopsScanning() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+        mController.refreshUi();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_deviceBonding_disablesGroup() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_deviceBonding_setsScanModeConnectable() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+        mController.refreshUi();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().getScanMode()).isEqualTo(
+                BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+    }
+
+    @Test
+    public void onStop_stopsScanning() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isFalse();
+    }
+
+    @Test
+    public void onStop_clearsNonBondedDevices() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mCachedDeviceManager).clearNonBondedDevices();
+    }
+
+    @Test
+    public void onStop_clearsGroup() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isGreaterThan(0);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onStop_setsScanModeConnectable() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().getScanMode()).isEqualTo(
+                BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+    }
+
+    @Test
+    public void discoverableScanModeTimeout_controllerStarted_resetsDiscoverableScanMode() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        BluetoothAdapter.getDefaultAdapter().setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+        mContext.sendBroadcast(new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().getScanMode()).isEqualTo(
+                BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+    }
+
+    @Test
+    public void discoverableScanModeTimeout_controllerStopped_doesNotResetDiscoverableScanMode() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        BluetoothAdapter.getDefaultAdapter().setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+        mContext.sendBroadcast(new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().getScanMode()).isEqualTo(
+                BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+
+    private static final class TestBluetoothScanningDevicesGroupPreferenceController extends
+            BluetoothScanningDevicesGroupPreferenceController {
+
+        private CachedBluetoothDevice mLastClickedDevice;
+
+        TestBluetoothScanningDevicesGroupPreferenceController(Context context,
+                String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected void onDeviceClickedInternal(CachedBluetoothDevice cachedDevice) {
+            mLastClickedDevice = cachedDevice;
+        }
+
+        CachedBluetoothDevice getLastClickedDevice() {
+            return mLastClickedDevice;
+        }
+
+        @Override
+        protected BluetoothDeviceFilter.Filter getDeviceFilter() {
+            return BluetoothDeviceFilter.ALL_FILTER;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothSettingsFragmentTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothSettingsFragmentTest.java
new file mode 100644
index 0000000..52c7078
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothSettingsFragmentTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.bluetooth.BluetoothAdapter.STATE_OFF;
+import static android.bluetooth.BluetoothAdapter.STATE_ON;
+import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF;
+import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
+import static android.os.UserManager.DISALLOW_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.Switch;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link BluetoothSettingsFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class BluetoothSettingsFragmentTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private Context mContext;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private FragmentController<BluetoothSettingsFragment> mFragmentController;
+    private BluetoothSettingsFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+
+        mContext = RuntimeEnvironment.application;
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(mContext, /* onInitCallback= */
+                null);
+        mFragment = new BluetoothSettingsFragment();
+        mFragmentController = FragmentController.of(mFragment);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void onStart_setsBluetoothManagerForegroundActivity() {
+        mFragmentController.create().start();
+
+        assertThat(mLocalBluetoothManager.getForegroundActivity()).isEqualTo(
+                mFragment.requireActivity());
+    }
+
+    @Test
+    public void onStart_initializesSwitchState() {
+        getShadowBluetoothAdapter().setState(STATE_ON);
+
+        mFragmentController.create().start();
+
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isTrue();
+    }
+
+    @Test
+    public void onStop_clearsBluetoothManagerForegroundActivity() {
+        mFragmentController.create().start().resume().pause().stop();
+
+        assertThat(mLocalBluetoothManager.getForegroundActivity()).isNull();
+    }
+
+    @Test
+    public void switchCheckedOn_enablesAdapter() {
+        mFragmentController.setup();
+        assertThat(BluetoothAdapter.getDefaultAdapter().isEnabled()).isFalse();
+
+        findSwitch(mFragment.requireActivity()).performClick();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isEnabled()).isTrue();
+    }
+
+    @Test
+    public void switchCheckedOff_disablesAdapter() {
+        getShadowBluetoothAdapter().setState(STATE_ON);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        mFragmentController.setup();
+        assertThat(BluetoothAdapter.getDefaultAdapter().isEnabled()).isTrue();
+
+        findSwitch(mFragment.requireActivity()).performClick();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isEnabled()).isFalse();
+    }
+
+    @Test
+    public void stateChanged_turningOn_setsSwitchChecked() {
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_TURNING_ON);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isTrue();
+    }
+
+    @Test
+    public void stateChanged_turningOn_setsSwitchDisabled() {
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_TURNING_ON);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void stateChanged_on_setsSwitchChecked() {
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_ON);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isTrue();
+    }
+
+    @Test
+    public void stateChanged_on_setsSwitchEnabled() {
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_ON);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void stateChanged_on_userRestricted_setsSwitchDisabled() {
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_BLUETOOTH)).thenReturn(true);
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_ON);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void stateChanged_turningOff_setsSwitchUnchecked() {
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_TURNING_OFF);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isFalse();
+    }
+
+    @Test
+    public void stateChanged_turningOff_setsSwitchDisabled() {
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_TURNING_OFF);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void stateChanged_off_setsSwitchUnchecked() {
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_OFF);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isFalse();
+    }
+
+    @Test
+    public void stateChanged_off_setsSwitchEnabled() {
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_OFF);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void stateChanged_off_userRestricted_setsSwitchDisabled() {
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_BLUETOOTH)).thenReturn(true);
+        mFragmentController.setup();
+
+        sendStateChangedIntent(STATE_OFF);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void stateChanged_fragmentStopped_doesNothing() {
+        mFragmentController.setup();
+        mFragmentController.stop();
+
+        sendStateChangedIntent(STATE_TURNING_ON);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isFalse();
+    }
+
+    private void sendStateChangedIntent(int state) {
+        Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+        intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
+        mContext.sendBroadcast(intent);
+    }
+
+    private Switch findSwitch(Activity activity) {
+        return activity.findViewById(R.id.toggle_switch);
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceControllerTest.java
new file mode 100644
index 0000000..8065703
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceControllerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Arrays;
+
+/** Unit test for {@link BluetoothUnbondedDevicesPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class BluetoothUnbondedDevicesPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private CachedBluetoothDevice mUnbondedCachedDevice;
+    @Mock
+    private BluetoothDevice mUnbondedDevice;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<BluetoothUnbondedDevicesPreferenceController>
+            mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        Context context = RuntimeEnvironment.application;
+
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */
+                null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        when(mUnbondedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mUnbondedCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mUnbondedCachedDevice.getDevice()).thenReturn(mUnbondedDevice);
+        BluetoothDevice bondedDevice = mock(BluetoothDevice.class);
+        when(bondedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        CachedBluetoothDevice bondedCachedDevice = mock(CachedBluetoothDevice.class);
+        when(bondedCachedDevice.getDevice()).thenReturn(bondedDevice);
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+                Arrays.asList(mUnbondedCachedDevice, bondedCachedDevice));
+
+        // Make sure controller is available.
+        Shadows.shadowOf(context.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                BluetoothUnbondedDevicesPreferenceController.class, mPreferenceGroup);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void showsOnlyUnbondedDevices() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        assertThat(devicePreference.getCachedDevice()).isEqualTo(mUnbondedCachedDevice);
+    }
+
+    @Test
+    public void onDeviceClicked_startsPairing() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        devicePreference.performClick();
+
+        verify(mUnbondedCachedDevice).startPairing();
+    }
+
+    @Test
+    public void onDeviceClicked_pairingStartFails_resumesScanning() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+        when(mUnbondedCachedDevice.startPairing()).thenReturn(false);
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+
+        devicePreference.performClick();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isDiscovering()).isTrue();
+    }
+
+    @Test
+    public void onDeviceClicked_requestsPhonebookAccess() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        when(mUnbondedCachedDevice.startPairing()).thenReturn(true);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        devicePreference.performClick();
+
+        verify(mUnbondedCachedDevice).setPhonebookPermissionChoice(
+                CachedBluetoothDevice.ACCESS_ALLOWED);
+    }
+
+    @Test
+    public void onDeviceClicked_requestsMessageAccess() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        when(mUnbondedCachedDevice.startPairing()).thenReturn(true);
+        BluetoothDevicePreference devicePreference =
+                (BluetoothDevicePreference) mPreferenceGroup.getPreference(0);
+
+        devicePreference.performClick();
+
+        verify(mUnbondedCachedDevice).setMessagePermissionChoice(
+                CachedBluetoothDevice.ACCESS_ALLOWED);
+    }
+
+    @Test
+    public void getAvailabilityStatus_disallowConfigBluetooth_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)).thenReturn(true);
+
+        assertThat(mControllerHelper.getController().getAvailabilityStatus()).isEqualTo(
+                DISABLED_FOR_USER);
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/LocalRenameDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/LocalRenameDialogFragmentTest.java
new file mode 100644
index 0000000..e994f3d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/LocalRenameDialogFragmentTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.bluetooth.BluetoothAdapter.EXTRA_LOCAL_NAME;
+import static android.bluetooth.BluetoothAdapter.STATE_ON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.widget.EditText;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowDialog;
+
+/** Unit test for {@link LocalRenameDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class LocalRenameDialogFragmentTest {
+
+    private static final String NAME = "name";
+    private static final String NAME_UPDATED = "name updated";
+
+    private LocalRenameDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        mFragment = new LocalRenameDialogFragment();
+        getShadowBluetoothAdapter().setState(STATE_ON);
+        BluetoothAdapter.getDefaultAdapter().enable();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void getDeviceName_adapterEnabled_returnsLocalAdapterName() {
+        BluetoothAdapter.getDefaultAdapter().setName(NAME);
+
+        assertThat(mFragment.getDeviceName()).isEqualTo(NAME);
+    }
+
+    @Test
+    public void getDeviceName_adapterDisabled_returnsNull() {
+        BluetoothAdapter.getDefaultAdapter().setName(NAME);
+        BluetoothAdapter.getDefaultAdapter().disable();
+
+        assertThat(mFragment.getDeviceName()).isNull();
+    }
+
+    @Test
+    public void localNameChangedBroadcast_updatesDeviceName() {
+        BluetoothAdapter.getDefaultAdapter().setName(NAME);
+        AlertDialog dialog = showDialog(mFragment);
+        EditText editText = dialog.findViewById(android.R.id.edit);
+        assertThat(editText.getText().toString()).isEqualTo(NAME);
+
+        BluetoothAdapter.getDefaultAdapter().setName(NAME_UPDATED);
+        Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+        intent.putExtra(EXTRA_LOCAL_NAME, NAME_UPDATED);
+        RuntimeEnvironment.application.sendBroadcast(intent);
+
+        assertThat(editText.getText().toString()).isEqualTo(NAME_UPDATED);
+        assertThat(dialog.getButton(DialogInterface.BUTTON_POSITIVE).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void setDeviceName_updatesLocalAdapterName() {
+        BluetoothAdapter.getDefaultAdapter().setName(NAME);
+        AlertDialog dialog = showDialog(mFragment);
+        EditText editText = dialog.findViewById(android.R.id.edit);
+
+        editText.setText(NAME_UPDATED);
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().getName()).isEqualTo(NAME_UPDATED);
+    }
+
+    private AlertDialog showDialog(LocalRenameDialogFragment fragment) {
+        BaseTestActivity activity = Robolectric.setupActivity(BaseTestActivity.class);
+        activity.showDialog(fragment, /* tag= */ null);
+        return (AlertDialog) ShadowDialog.getLatestDialog();
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/PairNewDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/PairNewDevicePreferenceControllerTest.java
new file mode 100644
index 0000000..fb425f2
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/PairNewDevicePreferenceControllerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_BLUETOOTH;
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+/** Unit test for {@link PairNewDevicePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowBluetoothAdapter.class,
+        ShadowBluetoothPan.class})
+public class PairNewDevicePreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private BluetoothEventManager mEventManager;
+    private BluetoothEventManager mSaveRealEventManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<PairNewDevicePreferenceController> mControllerHelper;
+    private PairNewDevicePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(mContext, /* onInitCallback= */
+                null);
+        mSaveRealEventManager = mLocalBluetoothManager.getEventManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mEventManager", mEventManager);
+
+        // Default to available.
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+
+        mPreference = new Preference(mContext);
+        mPreference.setIntent(new Intent());
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                PairNewDevicePreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mEventManager", mSaveRealEventManager);
+    }
+
+    @Test
+    public void checkInitialized_noFragmentOrIntent_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class,
+                () -> new PreferenceControllerTestHelper<>(mContext,
+                        PairNewDevicePreferenceController.class, new Preference(mContext)));
+    }
+
+    @Test
+    public void getAvailabilityStatus_bluetoothNotAvailable_unsupportedOnDevice() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_disallowBluetoothUserRestriction_disabledForUser() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_BLUETOOTH)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_disallowConfigBluetoothUserRestriction_disabledForUser() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_CONFIG_BLUETOOTH)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_available() {
+        Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+        // No user restrictions.
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void refreshUi_bluetoothAdapterEnabled_setsEmptySummary() {
+        BluetoothAdapter.getDefaultAdapter().enable();
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary().toString()).isEmpty();
+    }
+
+    @Test
+    public void refreshUi_bluetoothAdapterDisabled_setsTurnOnToPairSummary() {
+        BluetoothAdapter.getDefaultAdapter().disable();
+
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.bluetooth_pair_new_device_summary));
+    }
+
+    @Test
+    public void bluetoothAdapterStateChangedBroadcast_refreshesUi() {
+        BluetoothAdapter.getDefaultAdapter().enable();
+        mController.refreshUi();
+        assertThat(mPreference.getSummary().toString()).isEmpty();
+
+        BluetoothAdapter.getDefaultAdapter().disable();
+        mContext.sendBroadcast(new Intent(BluetoothAdapter.ACTION_STATE_CHANGED));
+
+        assertThat(mPreference.getSummary().toString()).isNotEmpty();
+    }
+
+    @Test
+    public void preferenceClicked_enablesAdapter() {
+        BluetoothAdapter.getDefaultAdapter().disable();
+
+        mPreference.performClick();
+
+        assertThat(BluetoothAdapter.getDefaultAdapter().isEnabled()).isTrue();
+    }
+
+    @Test
+    public void preferenceClicked_notHandled() {
+        assertThat(mPreference.getOnPreferenceClickListener().onPreferenceClick(
+                mPreference)).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/RemoteRenameDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/RemoteRenameDialogFragmentTest.java
new file mode 100644
index 0000000..305842f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/RemoteRenameDialogFragmentTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.DialogInterface;
+import android.widget.EditText;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+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;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowDialog;
+import org.robolectric.util.ReflectionHelpers;
+
+/** Unit test for {@link RemoteRenameDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class RemoteRenameDialogFragmentTest {
+
+    private static final String NAME = "name";
+    private static final String NAME_UPDATED = "name updated";
+
+    @Mock
+    private CachedBluetoothDevice mCachedDevice;
+    @Mock
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private CachedBluetoothDeviceManager mSaveRealCachedDeviceManager;
+    private LocalBluetoothManager mLocalBluetoothManager;
+    private RemoteRenameDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(
+                RuntimeEnvironment.application, /* onInitCallback= */ null);
+        mSaveRealCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mCachedDeviceManager);
+
+        String address = "00:11:22:33:AA:BB";
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        when(mCachedDeviceManager.findDevice(device)).thenReturn(mCachedDevice);
+        when(mCachedDevice.getAddress()).thenReturn(address);
+
+        mFragment = RemoteRenameDialogFragment.newInstance(mCachedDevice);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+        ReflectionHelpers.setField(mLocalBluetoothManager, "mCachedDeviceManager",
+                mSaveRealCachedDeviceManager);
+    }
+
+    @Test
+    public void getDeviceName_returnsCachedDeviceName() {
+        when(mCachedDevice.getName()).thenReturn(NAME);
+        showDialog(mFragment); // Attach the fragment.
+
+        assertThat(mFragment.getDeviceName()).isEqualTo(NAME);
+    }
+
+    @Test
+    public void setDeviceName_updatesCachedDeviceName() {
+        when(mCachedDevice.getName()).thenReturn(NAME);
+        AlertDialog dialog = showDialog(mFragment);
+        EditText editText = dialog.findViewById(android.R.id.edit);
+
+        editText.setText(NAME_UPDATED);
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        verify(mCachedDevice).setName(NAME_UPDATED);
+    }
+
+    private AlertDialog showDialog(RemoteRenameDialogFragment fragment) {
+        BaseTestActivity activity = Robolectric.setupActivity(BaseTestActivity.class);
+        activity.showDialog(fragment, /* tag= */ null);
+        return (AlertDialog) ShadowDialog.getLatestDialog();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/bluetooth/Utf8ByteLengthFilterTest.java b/tests/robotests/src/com/android/car/settings/bluetooth/Utf8ByteLengthFilterTest.java
new file mode 100644
index 0000000..9449a01
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/bluetooth/Utf8ByteLengthFilterTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2018 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.car.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.text.InputFilter;
+import android.text.SpannableStringBuilder;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link Utf8ByteLengthFilter}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class Utf8ByteLengthFilterTest {
+
+    @Test
+    public void filter_belowMaxBytes_returnsNull() {
+        CharSequence source = "1"; // 1 byte.
+        SpannableStringBuilder dest = new SpannableStringBuilder("abcdefgh"); // 8 bytes.
+        InputFilter lengthFilter = new Utf8ByteLengthFilter(10);
+
+        // Append source to dest.
+        CharSequence filtered = lengthFilter.filter(source, /* start= */ 0, source.length(), dest,
+                dest.length(), dest.length());
+
+        // Source is not filtered.
+        assertThat(filtered).isNull();
+    }
+
+    @Test
+    public void filter_maxBytes_returnsNull() {
+        CharSequence source = "1"; // 1 byte.
+        SpannableStringBuilder dest = new SpannableStringBuilder("abcdefghi"); // 9 bytes.
+        InputFilter lengthFilter = new Utf8ByteLengthFilter(10);
+
+        // Append source to dest.
+        CharSequence filtered = lengthFilter.filter(source, /* start= */ 0, source.length(), dest,
+                dest.length(), dest.length());
+
+        // Source is not filtered.
+        assertThat(filtered).isNull();
+    }
+
+    @Test
+    public void filter_aboveMaxBytes_returnsFilteredSource() {
+        CharSequence source = "12"; // 2 bytes.
+        SpannableStringBuilder dest = new SpannableStringBuilder("abcdefghi"); // 8 bytes.
+        InputFilter lengthFilter = new Utf8ByteLengthFilter(10);
+
+        // Append source to dest.
+        CharSequence filtered = lengthFilter.filter(source, /* start= */ 0, source.length(), dest,
+                dest.length(), dest.length());
+
+        // Source is filtered.
+        assertThat(filtered).isEqualTo("1");
+    }
+
+    // Borrowed from com.android.settings.bluetooth.Utf8ByteLengthFilterTest.
+    @Test
+    public void exerciseFilter() {
+        CharSequence source;
+        SpannableStringBuilder dest;
+        InputFilter lengthFilter = new Utf8ByteLengthFilter(10);
+        InputFilter[] filters = {lengthFilter};
+
+        source = "abc";
+        dest = new SpannableStringBuilder("abcdefgh");
+        dest.setFilters(filters);
+
+        dest.insert(1, source);
+        String expectedString1 = "aabbcdefgh";
+        assertThat(dest.toString()).isEqualTo(expectedString1);
+
+        dest.replace(5, 8, source);
+        String expectedString2 = "aabbcabcgh";
+        assertThat(dest.toString()).isEqualTo(expectedString2);
+
+        dest.insert(2, source);
+        assertThat(dest.toString()).isEqualTo(expectedString2);
+
+        dest.delete(1, 3);
+        String expectedString3 = "abcabcgh";
+        assertThat(dest.toString()).isEqualTo(expectedString3);
+
+        dest.append("12345");
+        String expectedString4 = "abcabcgh12";
+        assertThat(dest.toString()).isEqualTo(expectedString4);
+
+        source = "\u60a8\u597d";  // 2 Chinese chars == 6 bytes in UTF-8
+        dest.replace(8, 10, source);
+        assertThat(dest.toString()).isEqualTo(expectedString3);
+
+        dest.replace(0, 1, source);
+        String expectedString5 = "\u60a8bcabcgh";
+        assertThat(dest.toString()).isEqualTo(expectedString5);
+
+        dest.replace(0, 4, source);
+        String expectedString6 = "\u60a8\u597dbcgh";
+        assertThat(dest.toString()).isEqualTo(expectedString6);
+
+        source = "\u00a3\u00a5";  // 2 Latin-1 chars == 4 bytes in UTF-8
+        dest.delete(2, 6);
+        dest.insert(0, source);
+        String expectedString7 = "\u00a3\u00a5\u60a8\u597d";
+        assertThat(dest.toString()).isEqualTo(expectedString7);
+
+        dest.replace(2, 3, source);
+        String expectedString8 = "\u00a3\u00a5\u00a3\u597d";
+        assertThat(dest.toString()).isEqualTo(expectedString8);
+
+        dest.replace(3, 4, source);
+        String expectedString9 = "\u00a3\u00a5\u00a3\u00a3\u00a5";
+        assertThat(dest.toString()).isEqualTo(expectedString9);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/BaseCarSettingsActivityTest.java b/tests/robotests/src/com/android/car/settings/common/BaseCarSettingsActivityTest.java
new file mode 100644
index 0000000..a763b80
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/BaseCarSettingsActivityTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.ShadowCar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+
+/** Unit test for {@link BaseCarSettingsActivity}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class BaseCarSettingsActivityTest {
+
+    private static final String TEST_TAG = "test_tag";
+
+    private Context mContext;
+    private ActivityController<TestBaseCarSettingsActivity> mActivityController;
+    private TestBaseCarSettingsActivity mActivity;
+
+    @Mock
+    private CarUxRestrictionsManager mMockCarUxRestrictionsManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        CarUxRestrictions noSetupRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* time= */ 0).build();
+        when(mMockCarUxRestrictionsManager.getCurrentCarUxRestrictions())
+                .thenReturn(noSetupRestrictions);
+        ShadowCar.setCarManager(Car.CAR_UX_RESTRICTION_SERVICE, mMockCarUxRestrictionsManager);
+        mContext = RuntimeEnvironment.application;
+        mActivityController = ActivityController.of(new TestBaseCarSettingsActivity());
+        mActivity = mActivityController.get();
+        mActivityController.create();
+    }
+
+    @Test
+    public void onPreferenceStartFragment_launchesFragment() {
+        Preference pref = new Preference(mContext);
+        pref.setFragment(TestFragment.class.getName());
+
+        mActivity.onPreferenceStartFragment(/* caller= */ null, pref);
+
+        assertThat(mActivity.getSupportFragmentManager().findFragmentById(
+                R.id.fragment_container)).isInstanceOf(TestFragment.class);
+    }
+
+    @Test
+    public void launchFragment_dialogFragment_throwsError() {
+        DialogFragment dialogFragment = new DialogFragment();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mActivity.launchFragment(dialogFragment));
+    }
+
+    @Test
+    public void showDialog_launchDialogFragment_noTag() {
+        DialogFragment dialogFragment = mock(DialogFragment.class);
+        mActivity.showDialog(dialogFragment, /* tag */ null);
+        verify(dialogFragment).show(mActivity.getSupportFragmentManager(), null);
+    }
+
+    @Test
+    public void showDialog_launchDialogFragment_withTag() {
+        DialogFragment dialogFragment = mock(DialogFragment.class);
+        mActivity.showDialog(dialogFragment, TEST_TAG);
+        verify(dialogFragment).show(mActivity.getSupportFragmentManager(), TEST_TAG);
+    }
+
+    @Test
+    public void findDialogByTag_retrieveOriginalDialog() {
+        DialogFragment dialogFragment = new DialogFragment();
+        mActivity.showDialog(dialogFragment, TEST_TAG);
+        assertThat(mActivity.findDialogByTag(TEST_TAG)).isEqualTo(dialogFragment);
+    }
+
+    @Test
+    public void findDialogByTag_notDialogFragment() {
+        TestFragment fragment = new TestFragment();
+        mActivity.getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
+                fragment, TEST_TAG).commit();
+        assertThat(mActivity.findDialogByTag(TEST_TAG)).isNull();
+    }
+
+    @Test
+    public void findDialogByTag_noSuchFragment() {
+        assertThat(mActivity.findDialogByTag(TEST_TAG)).isNull();
+    }
+
+    @Test
+    public void onUxRestrictionsChanged_topFragmentInBackStackHasUpdatedUxRestrictions() {
+        TestFragment fragmentA = new TestFragment();
+        TestFragment fragmentB = new TestFragment();
+
+        CarUxRestrictions oldUxRestrictions = new CarUxRestrictions.Builder(
+                /* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE,
+                /* timestamp= */ 0
+        ).build();
+
+        CarUxRestrictions newUxRestrictions = new CarUxRestrictions.Builder(
+                /* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP,
+                /* timestamp= */ 0
+        ).build();
+
+        mActivity.launchFragment(fragmentA);
+        mActivity.onUxRestrictionsChanged(oldUxRestrictions);
+        mActivity.launchFragment(fragmentB);
+        mActivity.onUxRestrictionsChanged(newUxRestrictions);
+
+        assertThat(fragmentB.getUxRestrictions().toString())
+                .isEqualTo(newUxRestrictions.toString());
+    }
+
+    @Test
+    public void onBackStackChanged_uxRestrictionsChanged_currentFragmentHasUpdatedUxRestrictions() {
+        TestFragment fragmentA = new TestFragment();
+        TestFragment fragmentB = new TestFragment();
+
+        CarUxRestrictions oldUxRestrictions = new CarUxRestrictions.Builder(
+                /* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE,
+                /* timestamp= */ 0
+        ).build();
+
+        CarUxRestrictions newUxRestrictions = new CarUxRestrictions.Builder(
+                /* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP,
+                /* timestamp= */ 0
+        ).build();
+
+        mActivity.launchFragment(fragmentA);
+        mActivity.onUxRestrictionsChanged(oldUxRestrictions);
+        mActivity.launchFragment(fragmentB);
+        mActivity.onUxRestrictionsChanged(newUxRestrictions);
+        mActivity.goBack();
+
+        assertThat(fragmentA.getUxRestrictions().toString())
+                .isEqualTo(newUxRestrictions.toString());
+    }
+
+    /** Simple instance of {@link BaseCarSettingsActivity}. */
+    private static class TestBaseCarSettingsActivity extends BaseCarSettingsActivity {
+
+        @Nullable
+        @Override
+        protected Fragment getInitialFragment() {
+            return new TestFragment();
+        }
+    }
+
+    /** Simple Fragment for testing use. */
+    public static class TestFragment extends Fragment implements
+            CarUxRestrictionsManager.OnUxRestrictionsChangedListener {
+        private CarUxRestrictions mCarUxRestrictions;
+
+        @Override
+        public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
+            mCarUxRestrictions = restrictionInfo;
+        }
+
+        public CarUxRestrictions getUxRestrictions() {
+            return mCarUxRestrictions;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/ButtonPreferenceTest.java b/tests/robotests/src/com/android/car/settings/common/ButtonPreferenceTest.java
new file mode 100644
index 0000000..156fe33
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/ButtonPreferenceTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.view.View;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ButtonPreference.OnButtonClickListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ButtonPreferenceTest {
+
+    private PreferenceViewHolder mViewHolder;
+    private ButtonPreference mButtonPreference;
+
+    @Before
+    public void setUp() {
+        View rootView = View.inflate(RuntimeEnvironment.application, R.layout.two_action_preference,
+                null);
+        mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+        mButtonPreference = new ButtonPreference(RuntimeEnvironment.application);
+    }
+
+    @Test
+    public void buttonClicked_callsListener() {
+        mButtonPreference.onBindViewHolder(mViewHolder);
+        OnButtonClickListener listener = mock(OnButtonClickListener.class);
+        mButtonPreference.setOnButtonClickListener(listener);
+
+        mViewHolder.findViewById(android.R.id.widget_frame).performClick();
+
+        verify(listener).onButtonClick(mButtonPreference);
+    }
+
+    @Test
+    public void performButtonClick_listenerSetAndButtonVisible_listenerFired() {
+        ButtonPreference.OnButtonClickListener listener = mock(
+                ButtonPreference.OnButtonClickListener.class);
+        mButtonPreference.setOnButtonClickListener(listener);
+        mButtonPreference.showAction(true);
+
+        mButtonPreference.performButtonClick();
+        verify(listener).onButtonClick(mButtonPreference);
+    }
+
+    @Test
+    public void performButtonClick_listenerSetAndButtonInvisible_listenerNotFired() {
+        ButtonPreference.OnButtonClickListener listener = mock(
+                ButtonPreference.OnButtonClickListener.class);
+        mButtonPreference.setOnButtonClickListener(listener);
+        mButtonPreference.showAction(false);
+
+        mButtonPreference.performButtonClick();
+        verify(listener, never()).onButtonClick(mButtonPreference);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/CarSettingActivityTest.java b/tests/robotests/src/com/android/car/settings/common/CarSettingActivityTest.java
new file mode 100644
index 0000000..09c464b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/CarSettingActivityTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.Car;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.datetime.DatetimeSettingsFragment;
+import com.android.car.settings.testutils.DummyFragment;
+import com.android.car.settings.testutils.ShadowCar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+
+/** Unit test for {@link CarSettingActivity}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class CarSettingActivityTest {
+
+    private static final String TEST_TAG = "test_tag";
+
+    private Context mContext;
+    private ActivityController<CarSettingActivity> mActivityController;
+    private CarSettingActivity mActivity;
+
+    @Mock
+    private CarUxRestrictionsManager mMockCarUxRestrictionsManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        CarUxRestrictions noSetupRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* time= */ 0).build();
+        when(mMockCarUxRestrictionsManager.getCurrentCarUxRestrictions())
+                .thenReturn(noSetupRestrictions);
+        ShadowCar.setCarManager(Car.CAR_UX_RESTRICTION_SERVICE, mMockCarUxRestrictionsManager);
+        mContext = RuntimeEnvironment.application;
+        mActivityController = ActivityController.of(new CarSettingActivity());
+        mActivity = mActivityController.get();
+        mActivityController.create();
+    }
+
+    @Test
+    public void launchWithIntent_resolveToFragment() {
+        MockitoAnnotations.initMocks(this);
+        Intent intent = new Intent(Settings.ACTION_DATE_SETTINGS);
+        CarSettingActivity activity =
+                Robolectric.buildActivity(CarSettingActivity.class, intent).setup().get();
+        assertThat(activity.getSupportFragmentManager().findFragmentById(R.id.fragment_container))
+                .isInstanceOf(DatetimeSettingsFragment.class);
+    }
+
+    @Test
+    public void launchWithEmptyIntent_resolveToDefaultFragment() {
+        CarSettingActivity activity =
+                Robolectric.buildActivity(CarSettingActivity.class).setup().get();
+        assertThat(activity.getSupportFragmentManager().findFragmentById(R.id.fragment_container))
+                .isInstanceOf(DummyFragment.class);
+    }
+
+    @Test
+    public void onResume_newIntent_launchesNewFragment() {
+        mActivityController.start().postCreate(null).resume();
+        TestFragment testFragment = new TestFragment();
+        mActivity.launchFragment(testFragment);
+        assertThat(mActivity.getCurrentFragment()).isEqualTo(testFragment);
+
+        mActivity.onNewIntent(new Intent(Settings.ACTION_DATE_SETTINGS));
+        mActivity.onResume();
+
+        assertThat(mActivity.getCurrentFragment()).isNotEqualTo(testFragment);
+    }
+
+    @Test
+    public void onResume_savedInstanceState_doesNotLaunchFragmentFromOldIntent() {
+        mActivityController.start().postCreate(null).resume();
+        Intent intent = new Intent(Settings.ACTION_DATE_SETTINGS);
+        mActivity.onNewIntent(intent);
+        assertThat(mActivity.getCurrentFragment()).isNotInstanceOf(TestFragment.class);
+        mActivity.onResume(); // Showing date time settings (old intent)
+        mActivity.launchFragment(new TestFragment()); // Replace with test fragment.
+
+        // Recreate with saved state (e.g. during config change).
+        Bundle outState = new Bundle();
+        mActivityController.pause().saveInstanceState(outState);
+        mActivityController = ActivityController.of(new CarSettingActivity(), intent);
+        mActivityController.setup(outState);
+
+        // Should still display most recently launched fragment.
+        assertThat(mActivityController.get().getCurrentFragment()).isInstanceOf(TestFragment.class);
+    }
+
+    @Test
+    public void launchFragment_rootFragment_clearsBackStack() {
+        // Add fragment 1
+        TestFragment testFragment1 = new TestFragment();
+        mActivity.launchFragment(testFragment1);
+
+        // Add fragment 2
+        TestFragment testFragment2 = new TestFragment();
+        mActivity.launchFragment(testFragment2);
+
+        // Add root fragment
+        Fragment root = Fragment.instantiate(mContext,
+                mContext.getString(R.string.config_settings_hierarchy_root_fragment));
+        mActivity.launchFragment(root);
+
+        assertThat(mActivity.getSupportFragmentManager().getBackStackEntryCount())
+                .isEqualTo(1);
+    }
+
+    /** Simple Fragment for testing use. */
+    public static class TestFragment extends Fragment {
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/ConfirmationDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/common/ConfirmationDialogFragmentTest.java
new file mode 100644
index 0000000..22ed78c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/ConfirmationDialogFragmentTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowDialog;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ConfirmationDialogFragmentTest {
+
+    private static final String TEST_ARG_KEY = "arg_key";
+    private static final String TEST_ARG_VALUE = "arg_value";
+    private static final String TEST_TITLE = "Test Title";
+    private static final String TEST_MESSAGE = "Test Message";
+
+    private ConfirmationDialogFragment.Builder mDialogFragmentBuilder;
+    private Fragment mFragment;
+    @Mock
+    private ConfirmationDialogFragment.ConfirmListener mConfirmListener;
+    @Mock
+    private ConfirmationDialogFragment.RejectListener mRejectListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mFragment = FragmentController.of(new Fragment()).setup();
+        mDialogFragmentBuilder = new ConfirmationDialogFragment.Builder(
+                RuntimeEnvironment.application);
+        mDialogFragmentBuilder.setTitle(TEST_TITLE);
+        mDialogFragmentBuilder.setMessage(TEST_MESSAGE);
+        mDialogFragmentBuilder.addArgumentString(TEST_ARG_KEY, TEST_ARG_VALUE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowDialog.reset();
+    }
+
+    @Test
+    public void buildDialogFragment_hasTitleAndMessage() {
+        ConfirmationDialogFragment dialogFragment = mDialogFragmentBuilder.build();
+        dialogFragment.show(mFragment.getFragmentManager(), ConfirmationDialogFragment.TAG);
+
+        assertThat(getShadowAlertDialog().getTitle()).isEqualTo(TEST_TITLE);
+        assertThat(getShadowAlertDialog().getMessage()).isEqualTo(TEST_MESSAGE);
+    }
+
+    @Test
+    public void buildDialogFragment_negativeButtonNotSet_negativeButtonNotVisible() {
+        mDialogFragmentBuilder.setPositiveButton(R.string.test_positive_button_label, null);
+        ConfirmationDialogFragment dialogFragment = mDialogFragmentBuilder.build();
+        dialogFragment.show(mFragment.getFragmentManager(), ConfirmationDialogFragment.TAG);
+
+        AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog();
+        assertThat(dialog.getButton(DialogInterface.BUTTON_POSITIVE).getVisibility()).isEqualTo(
+                View.VISIBLE);
+        assertThat(dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getVisibility()).isEqualTo(
+                View.GONE);
+    }
+
+    @Test
+    public void buildDialogFragment_positiveButtonNotSet_positiveButtonNotVisible() {
+        mDialogFragmentBuilder.setNegativeButton(R.string.test_negative_button_label, null);
+        ConfirmationDialogFragment dialogFragment = mDialogFragmentBuilder.build();
+        dialogFragment.show(mFragment.getFragmentManager(), ConfirmationDialogFragment.TAG);
+
+        AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog();
+        assertThat(dialog.getButton(DialogInterface.BUTTON_POSITIVE).getVisibility()).isEqualTo(
+                View.GONE);
+        assertThat(dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getVisibility()).isEqualTo(
+                View.VISIBLE);
+    }
+
+    @Test
+    public void clickPositiveButton_callsCallbackWithArgs() {
+        mDialogFragmentBuilder.setPositiveButton(R.string.test_positive_button_label,
+                mConfirmListener);
+        ConfirmationDialogFragment dialogFragment = mDialogFragmentBuilder.build();
+        dialogFragment.show(mFragment.getFragmentManager(), ConfirmationDialogFragment.TAG);
+
+        AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog();
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+        ArgumentCaptor<Bundle> bundle = ArgumentCaptor.forClass(Bundle.class);
+        verify(mConfirmListener).onConfirm(bundle.capture());
+        assertThat(bundle.getValue().getString(TEST_ARG_KEY)).isEqualTo(TEST_ARG_VALUE);
+    }
+
+    @Test
+    public void clickNegativeButton_callsCallbackWithArgs() {
+        mDialogFragmentBuilder.setNegativeButton(R.string.test_negative_button_label,
+                mRejectListener);
+        ConfirmationDialogFragment dialogFragment = mDialogFragmentBuilder.build();
+        dialogFragment.show(mFragment.getFragmentManager(), ConfirmationDialogFragment.TAG);
+
+        AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog();
+        dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
+        ArgumentCaptor<Bundle> bundle = ArgumentCaptor.forClass(Bundle.class);
+        verify(mRejectListener).onReject(bundle.capture());
+        assertThat(bundle.getValue().getString(TEST_ARG_KEY)).isEqualTo(TEST_ARG_VALUE);
+    }
+
+    private ShadowAlertDialog getShadowAlertDialog() {
+        return ShadowApplication.getInstance().getLatestAlertDialog();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/EditTextPreferenceDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/common/EditTextPreferenceDialogFragmentTest.java
new file mode 100644
index 0000000..bad3d92
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/EditTextPreferenceDialogFragmentTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.EditText;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowWindow;
+
+/** Unit test for {@link EditTextPreferenceDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class EditTextPreferenceDialogFragmentTest {
+
+    private Context mContext;
+    private ActivityController<BaseTestActivity> mTestActivityController;
+    private BaseTestActivity mTestActivity;
+    private EditTextPreference mPreference;
+    private EditTextPreferenceDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mTestActivityController = ActivityController.of(new BaseTestActivity());
+        mTestActivity = mTestActivityController.get();
+        mTestActivityController.setup();
+        TestTargetFragment targetFragment = new TestTargetFragment();
+        mTestActivity.launchFragment(targetFragment);
+        mPreference = new EditTextPreference(mContext);
+        mPreference.setDialogLayoutResource(R.layout.preference_dialog_edittext);
+        mPreference.setKey("key");
+        targetFragment.getPreferenceScreen().addPreference(mPreference);
+        mFragment = EditTextPreferenceDialogFragment
+                .newInstance(mPreference.getKey());
+
+        mFragment.setTargetFragment(targetFragment, /* requestCode= */ 0);
+    }
+
+    @Test
+    public void dialogPopulatedWithPreferenceText() {
+        mPreference.setText("text");
+
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        EditText editTextView = ShadowAlertDialog.getLatestAlertDialog().findViewById(
+                android.R.id.edit);
+
+        assertThat(editTextView.getText().toString()).isEqualTo(mPreference.getText());
+    }
+
+    @Test
+    public void softInputMethodSetOnWindow() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        assertThat(getShadowWindowFromDialog(
+                ShadowAlertDialog.getLatestAlertDialog()).getSoftInputMode()).isEqualTo(
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+    }
+
+    @Test
+    public void editTextHasFocus() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        EditText editTextView = ShadowAlertDialog.getLatestAlertDialog().findViewById(
+                android.R.id.edit);
+
+        assertThat(editTextView.hasFocus()).isTrue();
+    }
+
+    @Test
+    public void onDialogClosed_positiveResult_updatesPreference() {
+        String text = "text";
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        EditText editTextView = dialog.findViewById(android.R.id.edit);
+        editTextView.setText(text);
+
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        assertThat(mPreference.getText()).isEqualTo(text);
+    }
+
+    @Test
+    public void onDialogClosed_negativeResult_doesNothing() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        EditText editTextView = dialog.findViewById(android.R.id.edit);
+        editTextView.setText("text");
+
+        dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
+
+        assertThat(mPreference.getText()).isNull();
+    }
+
+    @Test
+    public void instanceStateRetained() {
+        String text = "text";
+        mPreference.setText(text);
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        // Save instance state.
+        Bundle outState = new Bundle();
+        mTestActivityController.pause().saveInstanceState(outState).stop();
+
+        // Recreate everything with saved state.
+        mTestActivityController = ActivityController.of(new BaseTestActivity());
+        mTestActivity = mTestActivityController.get();
+        mTestActivityController.setup(outState);
+
+        // Ensure saved text was applied.
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        EditText editTextView = dialog.findViewById(android.R.id.edit);
+        assertThat(editTextView.getText().toString()).isEqualTo(text);
+    }
+
+
+    private ShadowWindow getShadowWindowFromDialog(AlertDialog dialog) {
+        return (ShadowWindow) Shadow.extract(dialog.getWindow());
+    }
+
+    /** Simple {@link PreferenceFragmentCompat} implementation to serve as the target fragment. */
+    public static class TestTargetFragment extends PreferenceFragmentCompat {
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/ErrorDialogTest.java b/tests/robotests/src/com/android/car/settings/common/ErrorDialogTest.java
new file mode 100644
index 0000000..e7f7abb
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/ErrorDialogTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.RuntimeEnvironment.application;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.DialogTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+
+/**
+ * Tests for ErrorDialog.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ErrorDialogTest {
+    private static final String ERROR_DIALOG_TAG = "ErrorDialogTag";
+    private BaseTestActivity mTestActivity;
+    private Fragment mTestFragment;
+
+    @Before
+    public void setUpTestActivity() {
+        MockitoAnnotations.initMocks(this);
+
+        mTestActivity = Robolectric.setupActivity(BaseTestActivity.class);
+
+        mTestFragment = new Fragment();
+        mTestActivity.launchFragment(mTestFragment);
+    }
+
+    @Test
+    public void testOkDismissesDialog() {
+        ErrorDialog dialog = ErrorDialog.show(mTestFragment, R.string.delete_user_error_title);
+
+        assertThat(isDialogShown()).isTrue(); // Dialog is shown.
+
+        // Invoke cancel.
+        DialogTestUtils.clickPositiveButton(dialog);
+
+        assertThat(isDialogShown()).isFalse(); // Dialog is dismissed.
+    }
+
+    @Test
+    public void testErrorDialogSetsTitle() {
+        int testTitleId = R.string.add_user_error_title;
+        ErrorDialog dialog = ErrorDialog.show(mTestFragment, testTitleId);
+
+        assertThat(DialogTestUtils.getTitle(dialog)).isEqualTo(application.getString(testTitleId));
+    }
+
+    private boolean isDialogShown() {
+        return mTestActivity.getSupportFragmentManager()
+                .findFragmentByTag(ERROR_DIALOG_TAG) != null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/ExtraSettingsLoaderTest.java b/tests/robotests/src/com/android/car/settings/common/ExtraSettingsLoaderTest.java
new file mode 100644
index 0000000..36f6b25
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/ExtraSettingsLoaderTest.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.android.settingslib.drawer.CategoryKey.CATEGORY_DEVICE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Map;
+
+/** Unit test for {@link ExtraSettingsLoader}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class})
+public class ExtraSettingsLoaderTest {
+    private Context mContext;
+    private ExtraSettingsLoader mExtraSettingsLoader;
+    private static final String META_DATA_PREFERENCE_CATEGORY = "com.android.settings.category";
+    private static final String FAKE_CATEGORY = "fake_category";
+    private static final String FAKE_TITLE = "fake_title";
+    private static final String FAKE_SUMMARY = "fake_summary";
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        ShadowApplicationPackageManager.setResources(mContext.getResources());
+        mExtraSettingsLoader = new ExtraSettingsLoader(mContext);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationPackageManager.reset();
+    }
+
+    @Test
+    public void testLoadPreference_stringResources_shouldLoadResources() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+        Bundle bundle = new Bundle();
+        bundle.putString(META_DATA_PREFERENCE_TITLE, FAKE_TITLE);
+        bundle.putString(META_DATA_PREFERENCE_SUMMARY, FAKE_SUMMARY);
+        bundle.putString(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+        activityInfo.packageName = "package_name";
+        activityInfo.name = "class_name";
+
+        ResolveInfo resolveInfoSystem = new ResolveInfo();
+        resolveInfoSystem.system = true;
+        resolveInfoSystem.activityInfo = activityInfo;
+
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem);
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        assertThat(preferenceToBundleMap).hasSize(1);
+
+        for (Preference p : preferenceToBundleMap.keySet()) {
+            assertThat(p.getTitle()).isEqualTo(FAKE_TITLE);
+            assertThat(p.getSummary()).isEqualTo(FAKE_SUMMARY);
+        }
+    }
+
+    @Test
+    public void testLoadPreference_metadataBundleIsValue() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+        Bundle bundle = new Bundle();
+        bundle.putString(META_DATA_PREFERENCE_TITLE, FAKE_TITLE);
+        bundle.putString(META_DATA_PREFERENCE_SUMMARY, FAKE_SUMMARY);
+        bundle.putString(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+        activityInfo.packageName = "package_name";
+        activityInfo.name = "class_name";
+
+        ResolveInfo resolveInfoSystem = new ResolveInfo();
+        resolveInfoSystem.system = true;
+        resolveInfoSystem.activityInfo = activityInfo;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem);
+
+        ResolveInfo resolveInfoNonSystem = new ResolveInfo();
+        resolveInfoNonSystem.system = false;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoNonSystem);
+
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        assertThat(preferenceToBundleMap).hasSize(1);
+
+        for (Preference p : preferenceToBundleMap.keySet()) {
+            assertThat(p.getTitle()).isEqualTo(FAKE_TITLE);
+            assertThat(p.getSummary()).isEqualTo(FAKE_SUMMARY);
+
+            Bundle b = preferenceToBundleMap.get(p);
+            assertThat(b.getString(META_DATA_PREFERENCE_TITLE)).isEqualTo(FAKE_TITLE);
+            assertThat(b.getString(META_DATA_PREFERENCE_SUMMARY)).isEqualTo(FAKE_SUMMARY);
+            assertThat(b.getString(META_DATA_PREFERENCE_CATEGORY)).isEqualTo(FAKE_CATEGORY);
+            assertThat(b.getInt(META_DATA_PREFERENCE_ICON)).isNotNull();
+        }
+    }
+
+    @Test
+    public void testLoadPreference_integerResources_shouldLoadResources() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+        Bundle bundle = new Bundle();
+        bundle.putInt(META_DATA_PREFERENCE_TITLE, R.string.fake_title);
+        bundle.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.fake_summary);
+        bundle.putInt(META_DATA_PREFERENCE_CATEGORY, R.string.fake_category);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+        activityInfo.packageName = "package_name";
+        activityInfo.name = "class_name";
+
+        ResolveInfo resolveInfoSystem = new ResolveInfo();
+        resolveInfoSystem.system = true;
+        resolveInfoSystem.activityInfo = activityInfo;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem);
+
+        ResolveInfo resolveInfoNonSystem = new ResolveInfo();
+        resolveInfoNonSystem.system = false;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoNonSystem);
+
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        assertThat(preferenceToBundleMap).hasSize(1);
+
+        for (Preference p : preferenceToBundleMap.keySet()) {
+            assertThat(p.getTitle()).isEqualTo(FAKE_TITLE);
+            assertThat(p.getSummary()).isEqualTo(FAKE_SUMMARY);
+            assertThat(p.getIcon()).isNotNull();
+
+        }
+    }
+
+    @Test
+    public void testLoadPreference_noDefaultSummary() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+        Bundle bundle = new Bundle();
+        bundle.putString(META_DATA_PREFERENCE_TITLE, FAKE_TITLE);
+        bundle.putString(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+        activityInfo.packageName = "package_name";
+        activityInfo.name = "class_name";
+
+        ResolveInfo resolveInfoSystem = new ResolveInfo();
+        resolveInfoSystem.system = true;
+        resolveInfoSystem.activityInfo = activityInfo;
+
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem);
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        for (Preference p : preferenceToBundleMap.keySet()) {
+            assertThat(p.getTitle()).isEqualTo(FAKE_TITLE);
+            assertThat(p.getSummary()).isNull();
+
+        }
+    }
+
+    @Test
+    public void testLoadPreference_noCategory_shouldSetToDeviceCategory() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, CATEGORY_DEVICE);
+        Bundle bundle = new Bundle();
+        bundle.putString(META_DATA_PREFERENCE_TITLE, FAKE_TITLE);
+        bundle.putString(META_DATA_PREFERENCE_SUMMARY, FAKE_SUMMARY);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+        activityInfo.packageName = "package_name";
+        activityInfo.name = "class_name";
+
+        ResolveInfo resolveInfoSystem = new ResolveInfo();
+        resolveInfoSystem.system = true;
+        resolveInfoSystem.activityInfo = activityInfo;
+
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem);
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        assertThat(preferenceToBundleMap).hasSize(1);
+
+        for (Preference p : preferenceToBundleMap.keySet()) {
+            assertThat(p.getTitle()).isEqualTo(FAKE_TITLE);
+            assertThat(p.getSummary()).isEqualTo(FAKE_SUMMARY);
+        }
+    }
+
+    @Test
+    public void testLoadPreference_noCategoryMatched_shouldNotReturnPreferences() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+        Bundle bundle = new Bundle();
+        bundle.putString(META_DATA_PREFERENCE_TITLE, FAKE_TITLE);
+        bundle.putString(META_DATA_PREFERENCE_SUMMARY, FAKE_SUMMARY);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+        activityInfo.packageName = "package_name";
+        activityInfo.name = "class_name";
+
+        ResolveInfo resolveInfoSystem = new ResolveInfo();
+        resolveInfoSystem.system = true;
+        resolveInfoSystem.activityInfo = activityInfo;
+
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem);
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        assertThat(preferenceToBundleMap).isEmpty();
+    }
+
+    @Test
+    public void testLoadPreference_shouldLoadDefaultIcon() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+        Bundle bundle = new Bundle();
+        bundle.putString(META_DATA_PREFERENCE_TITLE, FAKE_TITLE);
+        bundle.putString(META_DATA_PREFERENCE_SUMMARY, FAKE_SUMMARY);
+        bundle.putString(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+        activityInfo.packageName = "package_name";
+        activityInfo.name = "class_name";
+
+        ResolveInfo resolveInfoSystem = new ResolveInfo();
+        resolveInfoSystem.system = true;
+        resolveInfoSystem.activityInfo = activityInfo;
+
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem);
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        for (Preference p : preferenceToBundleMap.keySet()) {
+            assertThat(p.getTitle()).isEqualTo(FAKE_TITLE);
+            assertThat(p.getSummary()).isEqualTo(FAKE_SUMMARY);
+            assertThat(p.getIcon()).isNotNull();
+        }
+    }
+
+    @Test
+    public void testLoadPreference_noSystemApp_returnsNoPreferences() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+        Bundle bundle = new Bundle();
+        bundle.putString(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+
+        ResolveInfo resolveInfoNonSystem1 = new ResolveInfo();
+        resolveInfoNonSystem1.system = false;
+        resolveInfoNonSystem1.activityInfo = activityInfo;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoNonSystem1);
+
+        ResolveInfo resolveInfoNonSystem2 = new ResolveInfo();
+        resolveInfoNonSystem2.system = false;
+        resolveInfoNonSystem2.activityInfo = activityInfo;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoNonSystem2);
+
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        assertThat(preferenceToBundleMap).isEmpty();
+    }
+
+    @Test
+    public void testLoadPreference_systemApp_returnsPreferences() {
+        Intent intent = new Intent();
+        intent.putExtra(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+        Bundle bundle = new Bundle();
+        bundle.putString(META_DATA_PREFERENCE_TITLE, FAKE_TITLE);
+        bundle.putString(META_DATA_PREFERENCE_SUMMARY, FAKE_SUMMARY);
+        bundle.putString(META_DATA_PREFERENCE_CATEGORY, FAKE_CATEGORY);
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.metaData = bundle;
+        activityInfo.packageName = "package_name";
+        activityInfo.name = "class_name";
+
+        ResolveInfo resolveInfoSystem1 = new ResolveInfo();
+        resolveInfoSystem1.system = true;
+        resolveInfoSystem1.activityInfo = activityInfo;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem1);
+
+        ResolveInfo resolveInfoNonSystem1 = new ResolveInfo();
+        resolveInfoNonSystem1.system = false;
+        resolveInfoNonSystem1.activityInfo = activityInfo;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoNonSystem1);
+
+        ResolveInfo resolveInfoSystem2 = new ResolveInfo();
+        resolveInfoSystem2.system = true;
+        resolveInfoSystem2.activityInfo = activityInfo;
+        getShadowPackageManager().addResolveInfoForIntent(intent, resolveInfoSystem2);
+
+        Map<Preference, Bundle> preferenceToBundleMap = mExtraSettingsLoader.loadPreferences(
+                intent);
+
+        assertThat(preferenceToBundleMap).hasSize(2);
+
+        for (Preference p : preferenceToBundleMap.keySet()) {
+            assertThat(p.getTitle()).isEqualTo(FAKE_TITLE);
+            assertThat(p.getSummary()).isEqualTo(FAKE_SUMMARY);
+        }
+    }
+
+    private ShadowApplicationPackageManager getShadowPackageManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
+
diff --git a/tests/robotests/src/com/android/car/settings/common/ExtraSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/common/ExtraSettingsPreferenceControllerTest.java
new file mode 100644
index 0000000..0ea2ef5
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/ExtraSettingsPreferenceControllerTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Unit test for {@link ExtraSettingsPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class})
+public class ExtraSettingsPreferenceControllerTest {
+
+    private static final Intent FAKE_INTENT = new Intent();
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private ExtraSettingsPreferenceController mController;
+    private PreferenceControllerTestHelper<ExtraSettingsPreferenceController>
+            mPreferenceControllerHelper;
+    private Map<Preference, Bundle> mPreferenceBundleMapEmpty = new HashMap<>();
+    private Map<Preference, Bundle> mPreferenceBundleMap = new HashMap<>();
+
+    @Mock
+    private ExtraSettingsLoader mExtraSettingsLoaderMock;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mPreferenceGroup.setIntent(FAKE_INTENT);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                ExtraSettingsPreferenceController.class, mPreferenceGroup);
+        mController = mPreferenceControllerHelper.getController();
+        Preference preference = new Preference(mContext);
+
+        Bundle bundle = new Bundle();
+        mPreferenceBundleMap = new HashMap<>();
+        mPreferenceBundleMap.put(preference, bundle);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationPackageManager.reset();
+    }
+
+    @Test
+    public void testRefreshUi_notInitializedYet() {
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testRefreshUi_initialized_noPreferenceAdded() {
+        when(mExtraSettingsLoaderMock.loadPreferences(FAKE_INTENT)).thenReturn(
+                mPreferenceBundleMapEmpty);
+
+        mController.setExtraSettingsLoader(mExtraSettingsLoaderMock);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+        assertThat(mPreferenceGroup.isVisible()).isEqualTo(false);
+    }
+
+    @Test
+    public void testRefreshUi_noPreferenceAdded_shouldNotBeVisible() {
+        when(mExtraSettingsLoaderMock.loadPreferences(FAKE_INTENT)).thenReturn(
+                mPreferenceBundleMapEmpty);
+
+        mController.setExtraSettingsLoader(mExtraSettingsLoaderMock);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isEqualTo(false);
+    }
+
+    @Test
+    public void testRefreshUi_initialized_preferenceAdded() {
+        when(mExtraSettingsLoaderMock.loadPreferences(FAKE_INTENT)).thenReturn(
+                mPreferenceBundleMap);
+
+        mController.setExtraSettingsLoader(mExtraSettingsLoaderMock);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testRefreshUi_preferenceAdded_shouldBeVisible() {
+        when(mExtraSettingsLoaderMock.loadPreferences(FAKE_INTENT)).thenReturn(
+                mPreferenceBundleMap);
+
+        mController.setExtraSettingsLoader(mExtraSettingsLoaderMock);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isEqualTo(true);
+    }
+
+    @Test
+    public void testRefreshUi_refreshedTwice_shouldOnlyAddPreferenceOnce() {
+        when(mExtraSettingsLoaderMock.loadPreferences(FAKE_INTENT)).thenReturn(
+                mPreferenceBundleMap);
+
+        mController.setExtraSettingsLoader(mExtraSettingsLoaderMock);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testRefreshUi_refreshedTwice_stillBeVisible() {
+        when(mExtraSettingsLoaderMock.loadPreferences(FAKE_INTENT)).thenReturn(
+                mPreferenceBundleMap);
+
+        mController.setExtraSettingsLoader(mExtraSettingsLoaderMock);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isEqualTo(true);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/FakePreferenceController.java b/tests/robotests/src/com/android/car/settings/common/FakePreferenceController.java
new file mode 100644
index 0000000..c8893cd
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/FakePreferenceController.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+/**
+ * Concrete {@link PreferenceController} with methods for verifying behavior in tests.
+ */
+public class FakePreferenceController extends PreferenceController<Preference> {
+
+    @AvailabilityStatus
+    private int mAvailabilityStatus;
+    private int mCheckInitializedCallCount;
+    private int mOnCreateInternalCallCount;
+    private int mOnStartInternalCallCount;
+    private int mOnResumeInternalCallCount;
+    private int mOnPauseInternalCallCount;
+    private int mOnStopInternalCallCount;
+    private int mOnDestroyInternalCallCount;
+    private int mUpdateStateCallCount;
+    private Preference mUpdateStateArg;
+    private int mHandlePreferenceChangedCallCount;
+    private Preference mHandlePreferenceChangedPreferenceArg;
+    private Object mHandlePreferenceChangedValueArg;
+    private int mHandlePreferenceClickedCallCount;
+    private Preference mHandlePreferenceClickedArg;
+
+    public FakePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mAvailabilityStatus = super.getAvailabilityStatus();
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void checkInitialized() {
+        mCheckInitializedCallCount++;
+    }
+
+    int getCheckInitializedCallCount() {
+        return mCheckInitializedCallCount;
+    }
+
+    @Override
+    @AvailabilityStatus
+    protected int getAvailabilityStatus() {
+        return mAvailabilityStatus;
+    }
+
+    void setAvailabilityStatus(@AvailabilityStatus int availabilityStatus) {
+        mAvailabilityStatus = availabilityStatus;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mOnCreateInternalCallCount++;
+    }
+
+    int getOnCreateInternalCallCount() {
+        return mOnCreateInternalCallCount;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mOnStartInternalCallCount++;
+    }
+
+    int getOnStartInternalCallCount() {
+        return mOnStartInternalCallCount;
+    }
+
+    @Override
+    protected void onResumeInternal() {
+        mOnResumeInternalCallCount++;
+    }
+
+    int getOnResumeInternalCallCount() {
+        return mOnResumeInternalCallCount;
+    }
+
+    @Override
+    protected void onPauseInternal() {
+        mOnPauseInternalCallCount++;
+    }
+
+    int getOnPauseInternalCallCount() {
+        return mOnPauseInternalCallCount;
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mOnStopInternalCallCount++;
+    }
+
+    int getOnStopInternalCallCount() {
+        return mOnStopInternalCallCount;
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mOnDestroyInternalCallCount++;
+    }
+
+    int getOnDestroyInternalCallCount() {
+        return mOnDestroyInternalCallCount;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        mUpdateStateArg = preference;
+        mUpdateStateCallCount++;
+    }
+
+    Preference getUpdateStateArg() {
+        return mUpdateStateArg;
+    }
+
+    int getUpdateStateCallCount() {
+        return mUpdateStateCallCount;
+    }
+
+    @Override
+    protected boolean handlePreferenceChanged(Preference preference, Object newValue) {
+        mHandlePreferenceChangedCallCount++;
+        mHandlePreferenceChangedPreferenceArg = preference;
+        mHandlePreferenceChangedValueArg = newValue;
+        return super.handlePreferenceChanged(preference, newValue);
+    }
+
+    int getHandlePreferenceChangedCallCount() {
+        return mHandlePreferenceChangedCallCount;
+    }
+
+    Preference getHandlePreferenceChangedPreferenceArg() {
+        return mHandlePreferenceChangedPreferenceArg;
+    }
+
+    Object getHandlePreferenceChangedValueArg() {
+        return mHandlePreferenceChangedValueArg;
+    }
+
+    @Override
+    protected boolean handlePreferenceClicked(Preference preference) {
+        mHandlePreferenceClickedCallCount++;
+        mHandlePreferenceClickedArg = preference;
+        return super.handlePreferenceClicked(preference);
+    }
+
+    int getHandlePreferenceClickedCallCount() {
+        return mHandlePreferenceClickedCallCount;
+    }
+
+    Preference getHandlePreferenceClickedArg() {
+        return mHandlePreferenceClickedArg;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/MasterSwitchPreferenceTest.java b/tests/robotests/src/com/android/car/settings/common/MasterSwitchPreferenceTest.java
new file mode 100644
index 0000000..7550967
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/MasterSwitchPreferenceTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class MasterSwitchPreferenceTest {
+
+    private PreferenceViewHolder mViewHolder;
+    private MasterSwitchPreference mMasterSwitchPreference;
+    @Mock
+    private MasterSwitchPreference.OnSwitchToggleListener mListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        View rootView = View.inflate(context, R.layout.two_action_preference, null);
+        View.inflate(context, R.layout.master_switch_widget,
+                (FrameLayout) rootView.findViewById(android.R.id.widget_frame));
+        mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+        mMasterSwitchPreference = new MasterSwitchPreference(context);
+
+        mMasterSwitchPreference.onBindViewHolder(mViewHolder);
+        mMasterSwitchPreference.setSwitchChecked(false);
+        mMasterSwitchPreference.setSwitchToggleListener(mListener);
+    }
+
+    @Test
+    public void widgetClicked_callsListener() {
+        mViewHolder.findViewById(android.R.id.widget_frame).performClick();
+
+        verify(mListener).onToggle(mMasterSwitchPreference, true);
+    }
+
+    @Test
+    public void widgetClicked_togglesSwitchState() {
+        mViewHolder.findViewById(android.R.id.widget_frame).performClick();
+
+        assertThat(mMasterSwitchPreference.isSwitchChecked()).isTrue();
+    }
+
+    @Test
+    public void setSwitchState_listenerSetAndButtonVisible_oppositeBool_callsListener() {
+        mMasterSwitchPreference.setSwitchChecked(true);
+        verify(mListener).onToggle(mMasterSwitchPreference, true);
+    }
+
+    @Test
+    public void setSwitchState_listenerSetAndButtonVisible_oppositeBool_togglesSwitchState() {
+        mMasterSwitchPreference.setSwitchChecked(true);
+        assertThat(mMasterSwitchPreference.isSwitchChecked()).isTrue();
+    }
+
+    @Test
+    public void setSwitchState_listenerSetAndButtonVisible_sameBool_listenerNotCalled() {
+        mMasterSwitchPreference.setSwitchChecked(false);
+        verify(mListener, never()).onToggle(eq(mMasterSwitchPreference), anyBoolean());
+    }
+
+    @Test
+    public void setSwitchState_listenerSetAndButtonInvisible_oppositeBool_listenerNotCalled() {
+        mMasterSwitchPreference.showAction(false);
+
+        mMasterSwitchPreference.setSwitchChecked(true);
+        verify(mListener, never()).onToggle(eq(mMasterSwitchPreference), anyBoolean());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/PasswordEditTextPreferenceDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/common/PasswordEditTextPreferenceDialogFragmentTest.java
new file mode 100644
index 0000000..647b641
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/PasswordEditTextPreferenceDialogFragmentTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadows.ShadowAlertDialog;
+
+/** Unit test for {@link EditTextPreferenceDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class PasswordEditTextPreferenceDialogFragmentTest {
+
+    private Context mContext;
+    private ActivityController<BaseTestActivity> mTestActivityController;
+    private BaseTestActivity mTestActivity;
+    private EditTextPreference mPreference;
+    private PasswordEditTextPreferenceDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mTestActivityController = ActivityController.of(new BaseTestActivity());
+        mTestActivity = mTestActivityController.get();
+        mTestActivityController.setup();
+        TestTargetFragment targetFragment = new TestTargetFragment();
+        mTestActivity.launchFragment(targetFragment);
+        mPreference = new PasswordEditTextPreference(mContext);
+        mPreference.setDialogLayoutResource(R.layout.preference_dialog_password_edittext);
+        mPreference.setKey("key");
+        targetFragment.getPreferenceScreen().addPreference(mPreference);
+        mFragment = PasswordEditTextPreferenceDialogFragment.newInstance(mPreference.getKey());
+        mFragment.setTargetFragment(targetFragment, /* requestCode= */ 0);
+    }
+
+    @Test
+    public void onStart_inputTypeSetToPassword_shouldRevealShowPasswordCheckBoxUnchecked() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        CheckBox checkBox = dialog.findViewById(R.id.checkbox);
+
+        assertThat(checkBox.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(!checkBox.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onCheckBoxChecked_shouldRevealRawPassword() {
+        String testPassword = "TEST_PASSWORD";
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        CheckBox checkBox = dialog.findViewById(R.id.checkbox);
+        EditText editText = dialog.findViewById(android.R.id.edit);
+        editText.setText(testPassword);
+        checkBox.performClick();
+
+        assertThat(editText.getInputType()).isEqualTo(InputType.TYPE_CLASS_TEXT
+                | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+        assertThat(editText.getText().toString()).isEqualTo(testPassword);
+    }
+
+    @Test
+    public void onCheckBoxUnchecked_shouldObscureRawPassword() {
+        String testPassword = "TEST_PASSWORD";
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        CheckBox checkBox = dialog.findViewById(R.id.checkbox);
+        EditText editText = dialog.findViewById(android.R.id.edit);
+        editText.setText(testPassword);
+        // Performing click twice to simulate uncheck
+        checkBox.performClick();
+        checkBox.performClick();
+
+        assertThat(editText.getInputType()).isEqualTo((InputType.TYPE_CLASS_TEXT
+                | InputType.TYPE_TEXT_VARIATION_PASSWORD));
+        assertThat(editText.getText().toString()).isEqualTo(testPassword);
+    }
+
+    /** Simple {@link PreferenceFragmentCompat} implementation to serve as the target fragment. */
+    public static class TestTargetFragment extends PreferenceFragmentCompat {
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/PreferenceControllerListHelperTest.java b/tests/robotests/src/com/android/car/settings/common/PreferenceControllerListHelperTest.java
new file mode 100644
index 0000000..9871f28
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/PreferenceControllerListHelperTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.car.drivingstate.CarUxRestrictions;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit test for {@link PreferenceControllerListHelper}.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class PreferenceControllerListHelperTest {
+
+    private static final CarUxRestrictions UX_RESTRICTIONS =
+            new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                    CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
+
+    @Test
+    public void getControllers_returnsList() {
+        List<String> validKeys = Arrays.asList("key1", "key2");
+        List<PreferenceController> controllers =
+                PreferenceControllerListHelper.getPreferenceControllersFromXml(
+                        RuntimeEnvironment.application,
+                        R.xml.preference_controller_list_helper_success,
+                        mock(FragmentController.class),
+                        UX_RESTRICTIONS);
+
+        assertThat(controllers).hasSize(validKeys.size());
+        List<String> foundKeys = new ArrayList<>();
+        for (PreferenceController controller : controllers) {
+            assertThat(controller).isInstanceOf(DefaultRestrictionsPreferenceController.class);
+            foundKeys.add(controller.getPreferenceKey());
+        }
+        assertThat(foundKeys).containsAllIn(validKeys);
+    }
+
+    @Test
+    public void getControllers_invalidController_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class,
+                () -> PreferenceControllerListHelper.getPreferenceControllersFromXml(
+                        RuntimeEnvironment.application,
+                        R.xml.preference_controller_list_helper_fail_invalid_controller,
+                        mock(FragmentController.class), UX_RESTRICTIONS));
+    }
+
+    @Test
+    public void getControllers_missingKey_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class,
+                () -> PreferenceControllerListHelper.getPreferenceControllersFromXml(
+                        RuntimeEnvironment.application,
+                        R.xml.preference_controller_list_helper_fail_missing_key,
+                        mock(FragmentController.class), UX_RESTRICTIONS));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/PreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/common/PreferenceControllerTest.java
new file mode 100644
index 0000000..3c1cef3
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/PreferenceControllerTest.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.CONDITIONALLY_UNAVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+/**
+ * Unit test for {@link PreferenceController}.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class PreferenceControllerTest {
+
+    private static final CarUxRestrictions LIMIT_STRINGS_UX_RESTRICTIONS =
+            new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                    CarUxRestrictions.UX_RESTRICTIONS_LIMIT_STRING_LENGTH, /* timestamp= */
+                    0).build();
+    private static final CarUxRestrictions NO_SETUP_UX_RESTRICTIONS =
+            new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                    CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP, /* timestamp= */ 0).build();
+
+    private static final CarUxRestrictions BASELINE_UX_RESTRICTIONS =
+            new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                    CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
+
+    private Context mContext;
+    @Mock
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<FakePreferenceController> mControllerHelper;
+    private FakePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                FakePreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @Test
+    public void setPreference_wrongType_throwsIllegalArgumentException() {
+        PreferenceControllerTestHelper<WrongTypePreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext, WrongTypePreferenceController.class);
+
+        assertThrows(IllegalArgumentException.class, () -> controllerHelper.setPreference(
+                new Preference(mContext)));
+    }
+
+    @Test
+    public void setPreference_correctType_setsPreference() {
+        assertThat(mController.getPreference()).isEqualTo(mPreference);
+    }
+
+    @Test
+    public void setPreference_callsCheckInitialized() {
+        assertThat(mController.getCheckInitializedCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void setPreference_registersOnPreferenceChangeListener() {
+        ArgumentCaptor<Preference.OnPreferenceChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(Preference.OnPreferenceChangeListener.class);
+        Object newValue = new Object();
+
+        verify(mPreference).setOnPreferenceChangeListener(listenerArgumentCaptor.capture());
+        listenerArgumentCaptor.getValue().onPreferenceChange(mPreference, newValue);
+
+        assertThat(mController.getHandlePreferenceChangedCallCount()).isEqualTo(1);
+        assertThat(mController.getHandlePreferenceChangedPreferenceArg()).isEqualTo(mPreference);
+        assertThat(mController.getHandlePreferenceChangedValueArg()).isEqualTo(newValue);
+    }
+
+    @Test
+    public void setPreference_registersOnPreferenceClickListener() {
+        ArgumentCaptor<Preference.OnPreferenceClickListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(Preference.OnPreferenceClickListener.class);
+
+        verify(mPreference).setOnPreferenceClickListener(listenerArgumentCaptor.capture());
+        listenerArgumentCaptor.getValue().onPreferenceClick(mPreference);
+
+        assertThat(mController.getHandlePreferenceClickedCallCount()).isEqualTo(1);
+        assertThat(mController.getHandlePreferenceClickedArg()).isEqualTo(mPreference);
+    }
+
+    @Test
+    public void onUxRestrictionsChanged_updatesUxRestrictions() {
+        mController.onUxRestrictionsChanged(NO_SETUP_UX_RESTRICTIONS);
+
+        assertThat(mController.getUxRestrictions()).isEqualTo(NO_SETUP_UX_RESTRICTIONS);
+    }
+
+    @Test
+    public void onUxRestrictionsChanged_created_available_updatesState() {
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        mController.onUxRestrictionsChanged(LIMIT_STRINGS_UX_RESTRICTIONS);
+
+        // onCreate, onUxRestrictionsChanged.
+        assertThat(mController.getUpdateStateCallCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void onUxRestrictionsChanged_notCreated_available_doesNotUpdateState() {
+        mController.onUxRestrictionsChanged(LIMIT_STRINGS_UX_RESTRICTIONS);
+
+        assertThat(mController.getUpdateStateCallCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onUxRestrictionsChanged_created_restricted_preferenceDisabled() {
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        mController.onUxRestrictionsChanged(NO_SETUP_UX_RESTRICTIONS);
+
+        verify(mPreference).setEnabled(false);
+    }
+
+    @Test
+    public void onUxRestrictionsChanged_created_restricted_unrestricted_preferenceEnabled() {
+        InOrder orderVerifier = inOrder(mPreference);
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.onUxRestrictionsChanged(NO_SETUP_UX_RESTRICTIONS);
+        mController.onUxRestrictionsChanged(BASELINE_UX_RESTRICTIONS);
+
+        // setEnabled(true) called on Create.
+        orderVerifier.verify(mPreference).setEnabled(true);
+
+        // setEnabled(false) called with the first UXR change event.
+        orderVerifier.verify(mPreference).setEnabled(false);
+
+        // setEnabled(true) called with the second UXR change event.
+        orderVerifier.verify(mPreference).setEnabled(true);
+    }
+
+    @Test
+    public void getAvailabilityStatus_defaultsToAvailable() {
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void refreshUi_notCreated_doesNothing() {
+        mController.refreshUi();
+
+        verify(mPreference, never()).setVisible(anyBoolean());
+        assertThat(mController.getUpdateStateCallCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_created_available_preferenceShown() {
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        mController.refreshUi();
+
+        // onCreate, refreshUi.
+        verify(mPreference, times(2)).setVisible(true);
+    }
+
+    @Test
+    public void refreshUi_created_notAvailable_preferenceHidden() {
+        mController.setAvailabilityStatus(CONDITIONALLY_UNAVAILABLE);
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        mController.refreshUi();
+
+        // onCreate, refreshUi.
+        verify(mPreference, times(2)).setVisible(false);
+    }
+
+    @Test
+    public void refreshUi_created_available_updatesState() {
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        mController.refreshUi();
+
+        // onCreate, refreshUi.
+        assertThat(mController.getUpdateStateCallCount()).isEqualTo(2);
+        assertThat(mController.getUpdateStateArg()).isEqualTo(mPreference);
+    }
+
+    @Test
+    public void refreshUi_created_notAvailable_doesNotUpdateState() {
+        mController.setAvailabilityStatus(CONDITIONALLY_UNAVAILABLE);
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        mController.refreshUi();
+
+        assertThat(mController.getUpdateStateCallCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void lifecycle_unsupportedOnDevice_doesNotCallSubclassHooks() {
+        mController.setAvailabilityStatus(UNSUPPORTED_ON_DEVICE);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.markState(Lifecycle.State.DESTROYED);
+
+        assertThat(mController.getOnCreateInternalCallCount()).isEqualTo(0);
+        assertThat(mController.getOnStartInternalCallCount()).isEqualTo(0);
+        assertThat(mController.getOnResumeInternalCallCount()).isEqualTo(0);
+        assertThat(mController.getOnPauseInternalCallCount()).isEqualTo(0);
+        assertThat(mController.getOnStopInternalCallCount()).isEqualTo(0);
+        assertThat(mController.getOnDestroyInternalCallCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onCreate_unsupportedOnDevice_hidesPreference() {
+        mController.setAvailabilityStatus(UNSUPPORTED_ON_DEVICE);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        verify(mPreference).setVisible(false);
+    }
+
+    @Test
+    public void onCreate_callsSubclassHook() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mController.getOnCreateInternalCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onCreate_available_updatesState() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mController.getUpdateStateCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onStart_callsSubclassHook() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mController.getOnStartInternalCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onStart_available_updatesState() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        // onCreate, onStart.
+        assertThat(mController.getUpdateStateCallCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void onResume_callsSubclassHook() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_RESUME);
+
+        assertThat(mController.getOnResumeInternalCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onPause_callsSubclassHook() {
+        mControllerHelper.markState(Lifecycle.State.RESUMED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_PAUSE);
+
+        assertThat(mController.getOnPauseInternalCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onStop_callsSubclassHook() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(mController.getOnStopInternalCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onDestroy_callsSubclassHook() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        assertThat(mController.getOnDestroyInternalCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void handlePreferenceChanged_defaultReturnsTrue() {
+        assertThat(mController.handlePreferenceChanged(mPreference, new Object())).isTrue();
+    }
+
+    @Test
+    public void handlePreferenceClicked_defaultReturnsFalse() {
+        assertThat(mController.handlePreferenceClicked(mPreference)).isFalse();
+    }
+
+    /** For testing passing the wrong type of preference to the controller. */
+    private static class WrongTypePreferenceController extends
+            PreferenceController<PreferenceGroup> {
+
+        WrongTypePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected Class<PreferenceGroup> getPreferenceType() {
+            return PreferenceGroup.class;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/PreferenceControllerTestHelper.java b/tests/robotests/src/com/android/car/settings/common/PreferenceControllerTestHelper.java
new file mode 100644
index 0000000..96ee562
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/PreferenceControllerTestHelper.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.State.CREATED;
+import static androidx.lifecycle.Lifecycle.State.DESTROYED;
+import static androidx.lifecycle.Lifecycle.State.INITIALIZED;
+import static androidx.lifecycle.Lifecycle.State.RESUMED;
+import static androidx.lifecycle.Lifecycle.State.STARTED;
+
+import static org.mockito.Mockito.mock;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+/**
+ * Helper for testing {@link PreferenceController} classes.
+ *
+ * @param <T> the type of preference controller under test.
+ */
+public class PreferenceControllerTestHelper<T extends PreferenceController> {
+
+    private static final String PREFERENCE_KEY = "preference_key";
+    private static final CarUxRestrictions UX_RESTRICTIONS =
+            new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                    CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
+
+    private Lifecycle.State mState = INITIALIZED;
+
+    private final FragmentController mMockFragmentController;
+    private final T mPreferenceController;
+    private final PreferenceScreen mScreen;
+    private boolean mSetPreferenceCalled;
+
+    /**
+     * Constructs a new helper. Call {@link #setPreference(Preference)} once initialization on the
+     * controller is complete to associate the controller with a preference.
+     *
+     * @param context                  the {@link Context} to use to instantiate the preference
+     *                                 controller.
+     * @param preferenceControllerType the class type under test.
+     */
+    public PreferenceControllerTestHelper(Context context, Class<T> preferenceControllerType) {
+        mMockFragmentController = mock(FragmentController.class);
+        mPreferenceController = ReflectionHelpers.callConstructor(preferenceControllerType,
+                ClassParameter.from(Context.class, context),
+                ClassParameter.from(String.class, PREFERENCE_KEY),
+                ClassParameter.from(FragmentController.class, mMockFragmentController),
+                ClassParameter.from(CarUxRestrictions.class, UX_RESTRICTIONS));
+        mScreen = new PreferenceManager(context).createPreferenceScreen(context);
+    }
+
+    /**
+     * Convenience constructor for a new helper for controllers which do not need to do additional
+     * initialization before a preference is set.
+     *
+     * @param preference the {@link Preference} to associate with the controller.
+     */
+    public PreferenceControllerTestHelper(Context context, Class<T> preferenceControllerType,
+            Preference preference) {
+        this(context, preferenceControllerType);
+        setPreference(preference);
+    }
+
+    /**
+     * Associates the controller with the given preference. This should only be called once.
+     */
+    public void setPreference(Preference preference) {
+        if (mSetPreferenceCalled) {
+            throw new IllegalStateException(
+                    "setPreference should only be called once. Create a new helper if needed.");
+        }
+        preference.setKey(PREFERENCE_KEY);
+        mScreen.addPreference(preference);
+        mPreferenceController.setPreference(preference);
+        mSetPreferenceCalled = true;
+    }
+
+    /**
+     * Returns the {@link PreferenceController} of this helper.
+     */
+    public T getController() {
+        return mPreferenceController;
+    }
+
+    /**
+     * Returns a mock {@link FragmentController} that can be used to verify controller navigation
+     * and stub finding dialog fragments.
+     */
+    public FragmentController getMockFragmentController() {
+        return mMockFragmentController;
+    }
+
+    /**
+     * Sends a {@link Lifecycle.Event} to the controller. This is preferred over calling the
+     * controller's lifecycle methods directly as it ensures intermediate events are dispatched.
+     * For example, sending {@link Lifecycle.Event#ON_START} to an
+     * {@link Lifecycle.State#INITIALIZED} controller will dispatch
+     * {@link Lifecycle.Event#ON_CREATE} and {@link Lifecycle.Event#ON_START} while moving the
+     * controller to the {@link Lifecycle.State#STARTED} state.
+     */
+    public void sendLifecycleEvent(Lifecycle.Event event) {
+        markState(getStateAfter(event));
+    }
+
+    /**
+     * Move the {@link PreferenceController} to the given {@code state}. This is preferred over
+     * calling the controller's lifecycle methods directly as it ensures intermediate events are
+     * dispatched. For example, marking the {@link Lifecycle.State#STARTED} state on an
+     * {@link Lifecycle.State#INITIALIZED} controller will also send the
+     * {@link Lifecycle.Event#ON_CREATE} and {@link Lifecycle.Event#ON_START} events.
+     */
+    public void markState(Lifecycle.State state) {
+        while (mState != state) {
+            while (mState.compareTo(state) > 0) {
+                dispatchEvent(downEvent(mState));
+            }
+            while (mState.compareTo(state) < 0) {
+                dispatchEvent(upEvent(mState));
+            }
+        }
+    }
+
+    /*
+     * Ideally we would use androidx.lifecycle.LifecycleRegistry to drive the lifecycle changes.
+     * However, doing so led to test flakiness with an unknown root cause. We dispatch state
+     * changes manually for now, borrowing from LifecycleRegistry's implementation, pending
+     * further investigation.
+     */
+
+    @NonNull
+    private Lifecycle getLifecycle() {
+        throw new UnsupportedOperationException();
+    }
+
+    private void dispatchEvent(Lifecycle.Event event) {
+        switch (event) {
+            case ON_CREATE:
+                mScreen.onAttached();
+                mPreferenceController.onCreate(this::getLifecycle);
+                break;
+            case ON_START:
+                mPreferenceController.onStart(this::getLifecycle);
+                break;
+            case ON_RESUME:
+                mPreferenceController.onResume(this::getLifecycle);
+                break;
+            case ON_PAUSE:
+                mPreferenceController.onPause(this::getLifecycle);
+                break;
+            case ON_STOP:
+                mPreferenceController.onStop(this::getLifecycle);
+                break;
+            case ON_DESTROY:
+                mScreen.onDetached();
+                mPreferenceController.onDestroy(this::getLifecycle);
+                break;
+            case ON_ANY:
+                throw new IllegalArgumentException();
+        }
+
+        mState = getStateAfter(event);
+    }
+
+    private static Lifecycle.State getStateAfter(Lifecycle.Event event) {
+        switch (event) {
+            case ON_CREATE:
+            case ON_STOP:
+                return CREATED;
+            case ON_START:
+            case ON_PAUSE:
+                return STARTED;
+            case ON_RESUME:
+                return RESUMED;
+            case ON_DESTROY:
+                return DESTROYED;
+            case ON_ANY:
+                break;
+        }
+        throw new IllegalArgumentException("Unexpected event value " + event);
+    }
+
+    private static Lifecycle.Event downEvent(Lifecycle.State state) {
+        switch (state) {
+            case INITIALIZED:
+                throw new IllegalArgumentException();
+            case CREATED:
+                return ON_DESTROY;
+            case STARTED:
+                return ON_STOP;
+            case RESUMED:
+                return ON_PAUSE;
+            case DESTROYED:
+                throw new IllegalArgumentException();
+        }
+        throw new IllegalArgumentException("Unexpected state value " + state);
+    }
+
+    private static Lifecycle.Event upEvent(Lifecycle.State state) {
+        switch (state) {
+            case INITIALIZED:
+            case DESTROYED:
+                return ON_CREATE;
+            case CREATED:
+                return ON_START;
+            case STARTED:
+                return ON_RESUME;
+            case RESUMED:
+                throw new IllegalArgumentException();
+        }
+        throw new IllegalArgumentException("Unexpected state value " + state);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/PreferenceUtilTest.java b/tests/robotests/src/com/android/car/settings/common/PreferenceUtilTest.java
new file mode 100644
index 0000000..d95e631
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/PreferenceUtilTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class PreferenceUtilTest {
+
+    @Test
+    public void testCheckPreferenceType_true() {
+        Preference preference = new SwitchPreference(RuntimeEnvironment.application);
+        assertThat(PreferenceUtil.checkPreferenceType(preference, SwitchPreference.class)).isTrue();
+    }
+
+    @Test
+    public void testCheckPreferenceType_superclass_true() {
+        Preference preference = new SwitchPreference(RuntimeEnvironment.application);
+        assertThat(
+                PreferenceUtil.checkPreferenceType(preference, TwoStatePreference.class)).isTrue();
+    }
+
+    @Test
+    public void testCheckPreferenceType_false() {
+        Preference preference = new ListPreference(RuntimeEnvironment.application);
+        assertThat(
+                PreferenceUtil.checkPreferenceType(preference, TwoStatePreference.class)).isFalse();
+    }
+
+    // Test should succeed without throwing an exception.
+    @Test
+    public void testRequirePreferenceType_true() {
+        Preference preference = new SwitchPreference(RuntimeEnvironment.application);
+        PreferenceUtil.requirePreferenceType(preference, SwitchPreference.class);
+    }
+
+    // Test should succeed without throwing an exception.
+    @Test
+    public void testRequirePreferenceType_superclass_true() {
+        Preference preference = new SwitchPreference(RuntimeEnvironment.application);
+        PreferenceUtil.requirePreferenceType(preference, TwoStatePreference.class);
+    }
+
+    @Test
+    public void testRequirePreferenceType_false() {
+        Preference preference = new ListPreference(RuntimeEnvironment.application);
+        assertThrows(IllegalArgumentException.class,
+                () -> PreferenceUtil.requirePreferenceType(preference, TwoStatePreference.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/PreferenceXmlParserTest.java b/tests/robotests/src/com/android/car/settings/common/PreferenceXmlParserTest.java
new file mode 100644
index 0000000..e989e74
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/PreferenceXmlParserTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Unit test for {@link PreferenceXmlParser}.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class PreferenceXmlParserTest {
+
+    @Test
+    public void extractMetadata_keyAndControllerName() throws IOException, XmlPullParserException {
+        List<Bundle> metadata = PreferenceXmlParser.extractMetadata(
+                RuntimeEnvironment.application, R.xml.preference_parser,
+                PreferenceXmlParser.MetadataFlag.FLAG_NEED_KEY
+                        | PreferenceXmlParser.MetadataFlag.FLAG_NEED_PREF_CONTROLLER);
+
+        assertThat(metadata).hasSize(4);
+        for (Bundle bundle : metadata) {
+            assertThat(bundle.getString(PreferenceXmlParser.METADATA_KEY)).isNotNull();
+            assertThat(bundle.getString(PreferenceXmlParser.METADATA_CONTROLLER)).isNotNull();
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/ProgressBarPreferenceTest.java b/tests/robotests/src/com/android/car/settings/common/ProgressBarPreferenceTest.java
new file mode 100644
index 0000000..171d622
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/ProgressBarPreferenceTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ProgressBarPreferenceTest {
+
+    private static final String TEST_LABEL = "TEST_LABEL";
+
+    private Context mContext;
+    private PreferenceViewHolder mViewHolder;
+    private ProgressBarPreference mProgressBarPreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        View rootView = View.inflate(mContext, R.layout.progress_bar_preference,
+                /* root= */ null);
+        mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+        mProgressBarPreference = new ProgressBarPreference(mContext);
+    }
+
+    @Test
+    public void setMinLabel_setsText() {
+        mProgressBarPreference.setMinLabel(TEST_LABEL);
+        mProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getMinLabel().getText()).isEqualTo(TEST_LABEL);
+    }
+
+    @Test
+    public void setMaxLabel_setsText() {
+        mProgressBarPreference.setMaxLabel(TEST_LABEL);
+        mProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getMaxLabel().getText()).isEqualTo(TEST_LABEL);
+    }
+
+    @Test
+    public void setMin_setsMin() {
+        mProgressBarPreference.setMin(10);
+        mProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getProgressBar().getMin()).isEqualTo(10);
+    }
+
+    @Test
+    public void setMax_setsMax() {
+        mProgressBarPreference.setMax(1000);
+        mProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getProgressBar().getMax()).isEqualTo(1000);
+    }
+
+    @Test
+    public void setProgress_setsProgress() {
+        mProgressBarPreference.setProgress(40);
+        mProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getProgressBar().getProgress()).isEqualTo(40);
+    }
+
+    private ProgressBar getProgressBar() {
+        return (ProgressBar) mViewHolder.findViewById(android.R.id.progress);
+    }
+
+    private TextView getMinLabel() {
+        return (TextView) mViewHolder.findViewById(android.R.id.text1);
+    }
+
+    private TextView getMaxLabel() {
+        return (TextView) mViewHolder.findViewById(android.R.id.text2);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/SettingsFragmentTest.java b/tests/robotests/src/com/android/car/settings/common/SettingsFragmentTest.java
new file mode 100644
index 0000000..a19e3dd
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/SettingsFragmentTest.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2018 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.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.widget.FrameLayout;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.DummyFragment;
+import com.android.car.settings.testutils.FragmentController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link SettingsFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class SettingsFragmentTest {
+
+    private static final String TEST_TAG = "test_tag";
+
+    private Context mContext;
+    private FragmentController<TestSettingsFragment> mFragmentController;
+    private SettingsFragment mFragment;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mFragmentController = FragmentController.of(new TestSettingsFragment());
+        mFragment = mFragmentController.get();
+    }
+
+    @Test
+    public void use_returnsController() {
+        mFragmentController.setup();
+        assertThat(mFragment.use(FakePreferenceController.class,
+                R.string.tpk_fake_controller)).isNotNull();
+    }
+
+    @Test
+    public void onAttach_registersLifecycleObservers() {
+        mFragmentController.create();
+        FakePreferenceController controller = mFragment.use(FakePreferenceController.class,
+                R.string.tpk_fake_controller);
+        assertThat(controller.getOnCreateInternalCallCount()).isEqualTo(1);
+        mFragmentController.destroy();
+        assertThat(controller.getOnDestroyInternalCallCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onUxRestrictionsChanged_propagatesToControllers() {
+        mFragmentController.setup();
+        FakePreferenceController controller = mFragment.use(FakePreferenceController.class,
+                R.string.tpk_fake_controller);
+        CarUxRestrictions uxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD, /* timestamp= */ 0).build();
+        mFragment.onUxRestrictionsChanged(uxRestrictions);
+        assertThat(controller.getUxRestrictions()).isEqualTo(uxRestrictions);
+    }
+
+    @Test
+    public void onDisplayPreferenceDialog_editTextPreference_showsDialog() {
+        mFragmentController.setup();
+
+        mFragment.getPreferenceScreen().findPreference(
+                mContext.getString(R.string.tpk_edit_text_preference)).performClick();
+
+        assertThat(mFragment.getFragmentManager().findFragmentByTag(
+                SettingsFragment.DIALOG_FRAGMENT_TAG)).isInstanceOf(
+                EditTextPreferenceDialogFragment.class);
+    }
+
+    @Test
+    public void onDisplayPreferenceDialog_listPreference_showsDialog() {
+        mFragmentController.setup();
+
+        mFragment.getPreferenceScreen().findPreference(
+                mContext.getString(R.string.tpk_list_preference)).performClick();
+
+        assertThat(mFragment.getFragmentManager().findFragmentByTag(
+                SettingsFragment.DIALOG_FRAGMENT_TAG)).isInstanceOf(
+                SettingsListPreferenceDialogFragment.class);
+    }
+
+    @Test
+    public void onDisplayPreferenceDialog_unknownPreferenceType_throwsIllegalArgumentException() {
+        mFragmentController.setup();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mFragment.onDisplayPreferenceDialog(new Preference(mContext)));
+    }
+
+    @Test
+    public void onDisplayPreferenceDialog_alreadyShowing_doesNothing() {
+        mFragmentController.setup();
+
+        // Show a dialog.
+        mFragment.getPreferenceScreen().findPreference(
+                mContext.getString(R.string.tpk_edit_text_preference)).performClick();
+        assertThat(mFragment.getFragmentManager().findFragmentByTag(
+                SettingsFragment.DIALOG_FRAGMENT_TAG)).isInstanceOf(
+                EditTextPreferenceDialogFragment.class);
+
+        // Attempt to show another.
+        mFragment.getPreferenceScreen().findPreference(
+                mContext.getString(R.string.tpk_list_preference)).performClick();
+
+        // Assert only one shown at a time.
+        assertThat(mFragment.getFragmentManager().findFragmentByTag(
+                SettingsFragment.DIALOG_FRAGMENT_TAG)).isInstanceOf(
+                EditTextPreferenceDialogFragment.class);
+    }
+
+    @Test
+    public void launchFragment_otherFragment_opensFragment() {
+        mFragmentController.setup();
+        TestSettingsFragment otherFragment = new TestSettingsFragment();
+        mFragment.launchFragment(otherFragment);
+        assertThat(
+                mFragment.getFragmentManager().findFragmentById(R.id.fragment_container)).isEqualTo(
+                otherFragment);
+    }
+
+    @Test
+    public void launchFragment_dialogFragment_throwsError() {
+        mFragmentController.setup();
+        DialogFragment dialogFragment = new DialogFragment();
+        assertThrows(IllegalArgumentException.class,
+                () -> mFragment.launchFragment(dialogFragment));
+    }
+
+    @Test
+    public void showDialog_noTag_launchesDialogFragment() {
+        mFragmentController.setup();
+        DialogFragment dialogFragment = mock(DialogFragment.class);
+        mFragment.showDialog(dialogFragment, /* tag */ null);
+        verify(dialogFragment).show(mFragment.getFragmentManager(), null);
+    }
+
+    @Test
+    public void showDialog_withTag_launchesDialogFragment() {
+        mFragmentController.setup();
+        DialogFragment dialogFragment = mock(DialogFragment.class);
+        mFragment.showDialog(dialogFragment, TEST_TAG);
+        verify(dialogFragment).show(mFragment.getFragmentManager(), TEST_TAG);
+    }
+
+    @Test
+    public void findDialogByTag_retrieveOriginalDialog_returnsDialog() {
+        mFragmentController.setup();
+        DialogFragment dialogFragment = new DialogFragment();
+        mFragment.showDialog(dialogFragment, TEST_TAG);
+        assertThat(mFragment.findDialogByTag(TEST_TAG)).isEqualTo(dialogFragment);
+    }
+
+    @Test
+    public void findDialogByTag_notDialogFragment_returnsNull() {
+        mFragmentController.setup();
+        TestSettingsFragment fragment = new TestSettingsFragment();
+        mFragment.getFragmentManager().beginTransaction().add(fragment, TEST_TAG).commit();
+        assertThat(mFragment.findDialogByTag(TEST_TAG)).isNull();
+    }
+
+    @Test
+    public void findDialogByTag_noSuchFragment_returnsNull() {
+        mFragmentController.setup();
+        assertThat(mFragment.findDialogByTag(TEST_TAG)).isNull();
+    }
+
+    @Test
+    public void startActivityForResult_largeRequestCode_throwsError() {
+        mFragmentController.setup();
+        assertThrows(() -> mFragment.startActivityForResult(new Intent(), 0xffff,
+                mock(ActivityResultCallback.class)));
+    }
+
+    @Test
+    public void startActivityForResult_tooManyRequests_throwsError() {
+        mFragmentController.setup();
+        assertThrows(() -> {
+            for (int i = 0; i < 0xff; i++) {
+                mFragment.startActivityForResult(new Intent(), i,
+                        mock(ActivityResultCallback.class));
+            }
+        });
+    }
+
+    @Test
+    public void startIntentSenderForResult_largeRequestCode_throwsError() {
+        mFragmentController.setup();
+        assertThrows(
+                () -> mFragment.startIntentSenderForResult(
+                        mock(IntentSender.class), /* requestCode= */ 0xffff,
+                        /* fillInIntent= */null, /* flagsMask= */ 0,
+                        /* flagsValues= */0, /* options= */ null,
+                        mock(ActivityResultCallback.class)));
+    }
+
+    @Test
+    public void startIntentSenderForResult_tooManyRequests_throwsError() {
+        mFragmentController.setup();
+        assertThrows(() -> {
+            for (int i = 0; i < 0xff; i++) {
+                mFragment.startIntentSenderForResult(
+                        mock(IntentSender.class), /* requestCode= */ 0xffff,
+                        /* fillInIntent= */null, /* flagsMask= */ 0,
+                        /* flagsValues= */0, /* options= */ null,
+                        mock(ActivityResultCallback.class));
+            }
+        });
+    }
+
+    @Test
+    public void onActivityResult_hasValidRequestCode_triggersOnActivityResult() {
+        mFragmentController.setup();
+        ActivityResultCallback callback = mock(ActivityResultCallback.class);
+
+        int reqCode = 100;
+        int resCode = -1;
+        mFragment.startActivityForResult(new Intent(), reqCode, callback);
+        int fragmentReqCode = (1 << 8) + reqCode;
+        mFragment.onActivityResult(fragmentReqCode, resCode, new Intent());
+        verify(callback).processActivityResult(eq(reqCode), eq(resCode), any(Intent.class));
+    }
+
+    @Test
+    public void onActivityResult_wrongRequestCode_doesntTriggerOnActivityResult() {
+        mFragmentController.setup();
+        ActivityResultCallback callback = mock(ActivityResultCallback.class);
+
+        int reqCode = 100;
+        int resCode = -1;
+        mFragment.startActivityForResult(new Intent(), reqCode,
+                callback);
+        int fragmentReqCode = (2 << 8) + reqCode;
+        mFragment.onActivityResult(fragmentReqCode, resCode, new Intent());
+        verify(callback, never()).processActivityResult(anyInt(), anyInt(),
+                any(Intent.class));
+    }
+
+    @Test
+    public void onActivityCreated_hasAppIconIfRoot() {
+        mFragmentController.setup();
+        DummyFragment otherFragment = new DummyFragment();
+        mFragment.launchFragment(otherFragment);
+
+        FrameLayout actionBarContainer =
+                otherFragment.requireActivity().findViewById(R.id.action_bar);
+
+        assertThat(actionBarContainer.requireViewById(R.id.back_button).getTag(R.id.back_button))
+                .isEqualTo(R.drawable.ic_launcher_settings);
+    }
+
+    @Test
+    public void onActivityCreated_hasBackArrowIconIfNotRoot() {
+        mFragmentController.setup();
+
+        TestSettingsFragment otherFragment1 = new TestSettingsFragment();
+        mFragment.launchFragment(otherFragment1);
+
+        TestSettingsFragment otherFragment2 = new TestSettingsFragment();
+        mFragment.launchFragment(otherFragment2);
+
+        FrameLayout actionBarContainer =
+                otherFragment2.requireActivity().findViewById(R.id.action_bar);
+
+        assertThat(actionBarContainer.requireViewById(R.id.back_button).getTag(R.id.back_button))
+                .isEqualTo(R.drawable.ic_arrow_back);
+    }
+
+    /** Concrete {@link SettingsFragment} for testing. */
+    public static class TestSettingsFragment extends SettingsFragment {
+        @Override
+        protected int getPreferenceScreenResId() {
+            return R.xml.settings_fragment;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/SettingsListPreferenceDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/common/SettingsListPreferenceDialogFragmentTest.java
new file mode 100644
index 0000000..88310de
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/SettingsListPreferenceDialogFragmentTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowApplication;
+
+/** Unit test for {@link SettingsListPreferenceDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class SettingsListPreferenceDialogFragmentTest {
+
+    private ActivityController<BaseTestActivity> mTestActivityController;
+    private BaseTestActivity mTestActivity;
+    private ListPreference mPreference;
+    private SettingsListPreferenceDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        Context context = RuntimeEnvironment.application;
+
+        mTestActivityController = ActivityController.of(new BaseTestActivity());
+        mTestActivity = mTestActivityController.get();
+        mTestActivityController.setup();
+
+        EditTextPreferenceDialogFragmentTest.TestTargetFragment targetFragment =
+                new EditTextPreferenceDialogFragmentTest.TestTargetFragment();
+        mTestActivity.launchFragment(targetFragment);
+        mPreference = new ListPreference(context);
+        mPreference.setDialogLayoutResource(R.layout.preference_dialog_edittext);
+        mPreference.setKey("key");
+        mPreference.setEntries(R.array.entries);
+        mPreference.setEntryValues(R.array.entry_values);
+        targetFragment.getPreferenceScreen().addPreference(mPreference);
+
+        mFragment = SettingsListPreferenceDialogFragment.newInstance(mPreference.getKey());
+        mFragment.setTargetFragment(targetFragment, /* requestCode= */ 0);
+    }
+
+    @Test
+    public void dialogPopulatedWithPreferenceEntries() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        assertThat(getShadowAlertDialog().getItems()).isEqualTo(mPreference.getEntries());
+    }
+
+    @Test
+    public void itemSelected_dismissesDialog() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        getShadowAlertDialog().clickOnItem(1);
+
+        assertThat(getShadowAlertDialog().hasBeenDismissed()).isTrue();
+    }
+
+    @Test
+    public void itemSelected_setsPreferenceValue() {
+        mPreference.setValueIndex(0);
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        getShadowAlertDialog().clickOnItem(1);
+
+        assertThat(mPreference.getValue()).isEqualTo(mPreference.getEntryValues()[1]);
+    }
+
+    @Test
+    public void onDialogClosed_negativeResult_doesNothing() {
+        mPreference.setValueIndex(0);
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+
+        dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
+
+        assertThat(mPreference.getValue()).isEqualTo(mPreference.getEntryValues()[0]);
+    }
+
+    @Test
+    public void instanceStateRetained() {
+        mPreference.setValueIndex(0);
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        // Save instance state.
+        Bundle outState = new Bundle();
+        mTestActivityController.pause().saveInstanceState(outState).stop();
+
+        // Recreate everything with saved state.
+        mTestActivityController = ActivityController.of(new BaseTestActivity());
+        mTestActivity = mTestActivityController.get();
+        mTestActivityController.setup(outState);
+
+        // Ensure saved entries were applied.
+        assertThat(getShadowAlertDialog().getItems()).isEqualTo(mPreference.getEntries());
+    }
+
+    private ShadowAlertDialog getShadowAlertDialog() {
+        return ShadowApplication.getInstance().getLatestAlertDialog();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/SettingsPreferenceDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/common/SettingsPreferenceDialogFragmentTest.java
new file mode 100644
index 0000000..3de4496
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/SettingsPreferenceDialogFragmentTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.preference.DialogPreference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowWindow;
+
+/** Unit test for {@link SettingsPreferenceDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class SettingsPreferenceDialogFragmentTest {
+
+    private ActivityController<BaseTestActivity> mTestActivityController;
+    private BaseTestActivity mTestActivity;
+    private DialogPreference mPreference;
+    private TestSettingsPreferenceDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        Context context = RuntimeEnvironment.application;
+
+        mTestActivityController = ActivityController.of(new BaseTestActivity());
+        mTestActivity = mTestActivityController.get();
+        mTestActivityController.setup();
+
+        TestTargetFragment targetFragment = new TestTargetFragment();
+        mTestActivity.launchFragment(targetFragment);
+        mPreference = new TestDialogPreference(context);
+        mPreference.setKey("key");
+        targetFragment.getPreferenceScreen().addPreference(mPreference);
+
+        mFragment = TestSettingsPreferenceDialogFragment.newInstance(mPreference.getKey());
+        mFragment.setTargetFragment(targetFragment, /* requestCode= */ 0);
+    }
+
+    @Test
+    public void dialogFieldsPopulatedWithPreferenceFields() {
+        mPreference.setDialogTitle("title");
+        mPreference.setPositiveButtonText("positive button text");
+        mPreference.setNegativeButtonText("negative button text");
+        mPreference.setDialogMessage("dialog message");
+
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        assertThat(getShadowAlertDialog().getTitle()).isEqualTo(mPreference.getDialogTitle());
+        assertThat(ShadowAlertDialog.getLatestAlertDialog().getButton(
+                DialogInterface.BUTTON_POSITIVE).getText()).isEqualTo(
+                mPreference.getPositiveButtonText());
+        assertThat(ShadowAlertDialog.getLatestAlertDialog().getButton(
+                DialogInterface.BUTTON_NEGATIVE).getText()).isEqualTo(
+                mPreference.getNegativeButtonText());
+        assertThat(getShadowAlertDialog().getMessage()).isEqualTo(mPreference.getDialogMessage());
+    }
+
+    @Test
+    public void dialogMessage_messageViewShown() {
+        mPreference.setDialogTitle("title");
+        mPreference.setPositiveButtonText("positive button text");
+        mPreference.setNegativeButtonText("negative button text");
+        mPreference.setDialogMessage("dialog message");
+
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        View messageView = ShadowAlertDialog.getLatestAlertDialog().findViewById(
+                android.R.id.message);
+
+        assertThat(messageView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void noDialogMessage_messageViewHidden() {
+        mPreference.setDialogTitle("title");
+        mPreference.setPositiveButtonText("positive button text");
+        mPreference.setNegativeButtonText("negative button text");
+
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        View messageView = ShadowAlertDialog.getLatestAlertDialog().findViewById(
+                android.R.id.message);
+
+        assertThat(messageView.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void getPreference_returnsDialogRequestingPreference() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        assertThat(mFragment.getPreference()).isEqualTo(mPreference);
+    }
+
+    @Test
+    public void dialogClosed_positiveButton_callsOnDialogClosed() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        assertThat(mFragment.getDialogClosedResult()).isEqualTo(Boolean.TRUE);
+    }
+
+    @Test
+    public void dialogClosed_negativeButton_callsOnDialogClosed() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+
+        dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
+
+        assertThat(mFragment.getDialogClosedResult()).isEqualTo(Boolean.FALSE);
+    }
+
+    @Test
+    public void subclassNeedsInputMethod_softInputModeSetOnWindow() {
+        mFragment.setNeedsInputMethod(true);
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        assertThat(getShadowWindowFromDialog(
+                ShadowAlertDialog.getLatestAlertDialog()).getSoftInputMode()).isEqualTo(
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+    }
+
+    @Test
+    public void subclassDoesNotNeedInputMethod_noWindowSoftInputMode() {
+        mFragment.setNeedsInputMethod(false);
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        assertThat(getShadowWindowFromDialog(
+                ShadowAlertDialog.getLatestAlertDialog()).getSoftInputMode()).isEqualTo(0);
+    }
+
+    @Test
+    public void instanceStateRetained() {
+        String dialogTitle = "dialog title";
+        String positiveButtonText = "positive button text";
+        String negativeButtonText = "negative button text";
+        String dialogMessage = "dialog message";
+        mPreference.setDialogTitle(dialogTitle);
+        mPreference.setPositiveButtonText(positiveButtonText);
+        mPreference.setNegativeButtonText(negativeButtonText);
+        mPreference.setDialogMessage(dialogMessage);
+
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        // Save instance state.
+        Bundle outState = new Bundle();
+        mTestActivityController.pause().saveInstanceState(outState).stop();
+
+        // Recreate everything with saved state.
+        mTestActivityController = ActivityController.of(new BaseTestActivity());
+        mTestActivity = mTestActivityController.get();
+        mTestActivityController.setup(outState);
+
+        // Ensure saved fields were applied.
+        assertThat(getShadowAlertDialog().getTitle()).isEqualTo(dialogTitle);
+        assertThat(ShadowAlertDialog.getLatestAlertDialog().getButton(
+                DialogInterface.BUTTON_POSITIVE).getText()).isEqualTo(positiveButtonText);
+        assertThat(ShadowAlertDialog.getLatestAlertDialog().getButton(
+                DialogInterface.BUTTON_NEGATIVE).getText()).isEqualTo(negativeButtonText);
+        assertThat(getShadowAlertDialog().getMessage()).isEqualTo(dialogMessage);
+    }
+
+    private ShadowAlertDialog getShadowAlertDialog() {
+        return ShadowApplication.getInstance().getLatestAlertDialog();
+    }
+
+    private ShadowWindow getShadowWindowFromDialog(AlertDialog dialog) {
+        return (ShadowWindow) Shadow.extract(dialog.getWindow());
+    }
+
+    /** Concrete implementation of the fragment under test. */
+    public static class TestSettingsPreferenceDialogFragment extends
+            SettingsPreferenceDialogFragment {
+
+        private Boolean mDialogClosedResult;
+        private boolean mNeedsInputMethod;
+
+        static TestSettingsPreferenceDialogFragment newInstance(String key) {
+            TestSettingsPreferenceDialogFragment fragment =
+                    new TestSettingsPreferenceDialogFragment();
+            Bundle b = new Bundle(/* capacity= */ 1);
+            b.putString(ARG_KEY, key);
+            fragment.setArguments(b);
+            return fragment;
+        }
+
+        @Override
+        protected boolean needInputMethod() {
+            return mNeedsInputMethod;
+        }
+
+        void setNeedsInputMethod(boolean needsInputMethod) {
+            mNeedsInputMethod = needsInputMethod;
+        }
+
+        @Override
+        protected void onDialogClosed(boolean positiveResult) {
+            mDialogClosedResult = positiveResult;
+        }
+
+        Boolean getDialogClosedResult() {
+            return mDialogClosedResult;
+        }
+    }
+
+    /** Concrete implementation of {@link DialogPreference} for testing use. */
+    private static class TestDialogPreference extends DialogPreference {
+        TestDialogPreference(Context context) {
+            super(context);
+        }
+    }
+
+    /** Simple {@link PreferenceFragmentCompat} implementation to serve as the target fragment. */
+    public static class TestTargetFragment extends PreferenceFragmentCompat {
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/TwoActionPreferenceTest.java b/tests/robotests/src/com/android/car/settings/common/TwoActionPreferenceTest.java
new file mode 100644
index 0000000..e6f9513
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/TwoActionPreferenceTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.XmlRes;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class TwoActionPreferenceTest {
+
+    private static class TestTwoActionPreference extends TwoActionPreference {
+
+        TestTwoActionPreference(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onBindWidgetFrame(View actionContainer) {
+            // Intentionally empty.
+        }
+    }
+
+    private Context mContext;
+    private PreferenceViewHolder mViewHolder;
+    private TestTwoActionPreference mTestTwoActionPreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        View rootView = View.inflate(mContext, R.layout.two_action_preference,
+                null);
+        mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+        mTestTwoActionPreference = new TestTwoActionPreference(mContext);
+    }
+
+    @Test
+    public void showAction_true_buttonVisible() {
+        mTestTwoActionPreference.showAction(true);
+        mTestTwoActionPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mViewHolder.findViewById(
+                R.id.action_widget_container).getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void showAction_false_buttonGone() {
+        mTestTwoActionPreference.showAction(false);
+        mTestTwoActionPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mViewHolder.findViewById(
+                R.id.action_widget_container).getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void isActionShown_true() {
+        mTestTwoActionPreference.showAction(true);
+        assertThat(mTestTwoActionPreference.isActionShown()).isTrue();
+    }
+
+    @Test
+    public void isActionShown_false() {
+        mTestTwoActionPreference.showAction(false);
+        assertThat(mTestTwoActionPreference.isActionShown()).isFalse();
+    }
+
+    @Test
+    public void preferenceConstructed_attrDefined_actionShown() {
+        TestSettingsFragment fragment = TestSettingsFragment.newInstance(
+                R.xml.two_action_preference_shown);
+        FragmentController.of(fragment).setup();
+
+        TwoActionPreference preference =
+                (TwoActionPreference) fragment.getPreferenceScreen().findPreference(
+                        mContext.getString(R.string.tpk_two_action_preference));
+
+        assertThat(preference.isActionShown()).isTrue();
+    }
+
+    @Test
+    public void preferenceConstructed_defaultValue_actionShown() {
+        TestSettingsFragment fragment = TestSettingsFragment.newInstance(
+                R.xml.two_action_preference_action_shown_not_specified);
+        FragmentController.of(fragment).setup();
+
+        TwoActionPreference preference =
+                (TwoActionPreference) fragment.getPreferenceScreen().findPreference(
+                        mContext.getString(R.string.tpk_two_action_preference));
+
+        assertThat(preference.isActionShown()).isTrue();
+    }
+
+    @Test
+    public void preferenceConstructed_attrDefined_actionHidden() {
+        TestSettingsFragment fragment = TestSettingsFragment.newInstance(
+                R.xml.two_action_preference_hidden);
+        FragmentController.of(fragment).setup();
+
+        TwoActionPreference preference =
+                (TwoActionPreference) fragment.getPreferenceScreen().findPreference(
+                        mContext.getString(R.string.tpk_two_action_preference));
+
+        assertThat(preference.isActionShown()).isFalse();
+    }
+
+    /**
+     * Test settings fragment which creates the xml screen provided via {@link #newInstance(int)}.
+     */
+    public static class TestSettingsFragment extends SettingsFragment {
+
+        private static final String ARG_SCREEN_RES = "arg_screen_res";
+
+        @XmlRes
+        private int mScreenResId;
+
+        public static TestSettingsFragment newInstance(@XmlRes int screenResId) {
+            Bundle arguments = new Bundle();
+            arguments.putInt(ARG_SCREEN_RES, screenResId);
+
+            TestSettingsFragment fragment = new TestSettingsFragment();
+            fragment.setArguments(arguments);
+            return fragment;
+        }
+
+        @Override
+        public void onAttach(Context context) {
+            super.onAttach(context);
+            mScreenResId = getArguments().getInt(ARG_SCREEN_RES);
+        }
+
+        @Override
+        @XmlRes
+        protected int getPreferenceScreenResId() {
+            return mScreenResId;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/ValidatedEditTextPreferenceDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/common/ValidatedEditTextPreferenceDialogFragmentTest.java
new file mode 100644
index 0000000..59c0130
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/ValidatedEditTextPreferenceDialogFragmentTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowWindow;
+
+/** Unit test for {@link EditTextPreferenceDialogFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ValidatedEditTextPreferenceDialogFragmentTest {
+
+    private Context mContext;
+    private ActivityController<BaseTestActivity> mTestActivityController;
+    private BaseTestActivity mTestActivity;
+    private EditTextPreference mPreference;
+    private ValidatedEditTextPreferenceDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mTestActivityController = ActivityController.of(new BaseTestActivity());
+        mTestActivity = mTestActivityController.get();
+        mTestActivityController.setup();
+        TestTargetFragment targetFragment = new TestTargetFragment();
+        mTestActivity.launchFragment(targetFragment);
+        mPreference = new ValidatedEditTextPreference(mContext);
+        mPreference.setDialogLayoutResource(R.layout.preference_dialog_edittext);
+        mPreference.setKey("key");
+        targetFragment.getPreferenceScreen().addPreference(mPreference);
+        mFragment = ValidatedEditTextPreferenceDialogFragment
+                .newInstance(mPreference.getKey());
+
+        mFragment.setTargetFragment(targetFragment, /* requestCode= */ 0);
+    }
+
+    @Test
+    public void noValidatorSet_shouldEnablePositiveButton_and_allowEnterToSubmit() {
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        Button positiveButton = ShadowAlertDialog.getLatestAlertDialog().getButton(
+                DialogInterface.BUTTON_POSITIVE);
+        EditText editText = ShadowAlertDialog.getLatestAlertDialog().findViewById(
+                android.R.id.edit);
+
+        assertThat(positiveButton.isEnabled()).isTrue();
+        assertThat(mFragment.getAllowEnterToSubmit()).isTrue();
+
+        editText.setText("any text");
+        assertThat(positiveButton.isEnabled()).isTrue();
+        assertThat(mFragment.getAllowEnterToSubmit()).isTrue();
+    }
+
+    @Test
+    public void onInvalidInput_shouldDisablePositiveButton_and_disallowEnterToSubmit() {
+        ((ValidatedEditTextPreference) mPreference).setValidator(
+                new ValidatedEditTextPreference.Validator() {
+                    @Override
+                    public boolean isTextValid(String value) {
+                        return value.length() > 100;
+                    }
+                });
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        Button positiveButton = ShadowAlertDialog.getLatestAlertDialog().getButton(
+                DialogInterface.BUTTON_POSITIVE);
+        EditText editText = ShadowAlertDialog.getLatestAlertDialog().findViewById(
+                android.R.id.edit);
+        editText.setText("shorter than 100");
+
+        assertThat(positiveButton.isEnabled()).isFalse();
+        assertThat(mFragment.getAllowEnterToSubmit()).isFalse();
+    }
+
+    @Test
+    public void onValidInput_shouldEnablePositiveButton_and_allowEnterToSubmit() {
+        ((ValidatedEditTextPreference) mPreference).setValidator(
+                new ValidatedEditTextPreference.Validator() {
+                    @Override
+                    public boolean isTextValid(String value) {
+                        return value.length() > 1;
+                    }
+                });
+        mTestActivity.showDialog(mFragment, /* tag= */ null);
+
+        Button positiveButton = ShadowAlertDialog.getLatestAlertDialog().getButton(
+                DialogInterface.BUTTON_POSITIVE);
+        EditText editText = ShadowAlertDialog.getLatestAlertDialog().findViewById(
+                android.R.id.edit);
+        editText.setText("longer than 1");
+
+        assertThat(positiveButton.isEnabled()).isTrue();
+        assertThat(mFragment.getAllowEnterToSubmit()).isTrue();
+    }
+
+    private ShadowWindow getShadowWindowFromDialog(AlertDialog dialog) {
+        return (ShadowWindow) Shadow.extract(dialog.getWindow());
+    }
+
+    /** Simple {@link PreferenceFragmentCompat} implementation to serve as the target fragment. */
+    public static class TestTargetFragment extends PreferenceFragmentCompat {
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java
new file mode 100644
index 0000000..c94cfa8
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.INetworkStatsService;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.util.Pair;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowINetworkStatsServiceStub;
+import com.android.car.settings.testutils.ShadowNetworkPolicyEditor;
+import com.android.car.settings.testutils.ShadowNetworkPolicyManager;
+
+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;
+import org.robolectric.annotation.Config;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/** Unit test for {@link AppDataUsageFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowNetworkPolicyEditor.class, ShadowNetworkPolicyManager.class,
+        ShadowINetworkStatsServiceStub.class})
+public class AppDataUsageFragmentTest {
+
+    private static final String KEY_START = "start";
+    private static final String KEY_END = "end";
+
+    private AppDataUsageFragment mFragment;
+    private FragmentController<AppDataUsageFragment> mFragmentController;
+
+    @Mock
+    private NetworkPolicy mNetworkPolicy;
+
+    @Mock
+    private NetworkPolicyManager mNetworkPolicyManager;
+
+    @Mock
+    private INetworkStatsService mINetworkStatsService;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mFragment = new AppDataUsageFragment();
+        mFragmentController = FragmentController.of(mFragment);
+
+        ShadowNetworkPolicyManager.setNetworkPolicyManager(mNetworkPolicyManager);
+        ShadowINetworkStatsServiceStub.setINetworkStatsSession(mINetworkStatsService);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowNetworkPolicyEditor.reset();
+        ShadowINetworkStatsServiceStub.reset();
+        ShadowNetworkPolicyManager.reset();
+    }
+
+    @Test
+    public void onActivityCreated_policyIsNull_startAndEndDateShouldHaveFourWeeksDifference() {
+        mFragmentController.create();
+
+        Bundle bundle = mFragment.getBundle();
+        long start = bundle.getLong(KEY_START);
+        long end = bundle.getLong(KEY_END);
+        long timeDiff = end - start;
+
+        assertThat(timeDiff).isEqualTo(DateUtils.WEEK_IN_MILLIS * 4);
+    }
+
+    @Test
+    public void onActivityCreated_iteratorIsEmpty_startAndEndDateShouldHaveFourWeeksDifference() {
+        ShadowNetworkPolicyEditor.setNetworkPolicy(mNetworkPolicy);
+
+        ArrayList<Pair<ZonedDateTime, ZonedDateTime>> list = new ArrayList<>();
+        Iterator iterator = list.iterator();
+        ShadowNetworkPolicyManager.setCycleIterator(iterator);
+        mFragmentController.create();
+
+        Bundle bundle = mFragment.getBundle();
+        long start = bundle.getLong(KEY_START);
+        long end = bundle.getLong(KEY_END);
+        long timeDiff = end - start;
+
+        assertThat(timeDiff).isEqualTo(DateUtils.WEEK_IN_MILLIS * 4);
+    }
+
+    @Test
+    public void onActivityCreated_iteratorIsNotEmpty_startAndEndDateShouldBeLastOneInIterator() {
+        ShadowNetworkPolicyEditor.setNetworkPolicy(mNetworkPolicy);
+
+        ZonedDateTime start1 = ZonedDateTime.now();
+        ZonedDateTime end1 = ZonedDateTime.now();
+        ZonedDateTime start2 = ZonedDateTime.now();
+        ZonedDateTime end2 = ZonedDateTime.now();
+
+        Pair pair1 = new Pair(start1, end1);
+        Pair pair2 = new Pair(start2, end2);
+
+        ArrayList<Pair<ZonedDateTime, ZonedDateTime>> list = new ArrayList<>();
+        list.add(pair1);
+        list.add(pair2);
+
+        Iterator iterator = list.iterator();
+        ShadowNetworkPolicyManager.setCycleIterator(iterator);
+        mFragmentController.create();
+
+        Bundle bundle = mFragment.getBundle();
+        long start = bundle.getLong(KEY_START);
+        long end = bundle.getLong(KEY_END);
+
+        assertThat(start).isEqualTo(start2.toInstant().toEpochMilli());
+        assertThat(end).isEqualTo(end2.toInstant().toEpochMilli());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/AppDataUsagePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsagePreferenceControllerTest.java
new file mode 100644
index 0000000..202b093
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsagePreferenceControllerTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static android.net.TrafficStats.UID_TETHERING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.net.NetworkStats;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ProgressBarPreference;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUidDetailProvider;
+import com.android.settingslib.net.UidDetail;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link AppDataUsagePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUidDetailProvider.class})
+public class AppDataUsagePreferenceControllerTest {
+
+    private static final int USER_ID = 10;
+
+    private Context mContext;
+    private LogicalPreferenceGroup mLogicalPreferenceGroup;
+    private AppDataUsagePreferenceController mController;
+    private PreferenceControllerTestHelper<AppDataUsagePreferenceController>
+            mPreferenceControllerHelper;
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Mock
+    private UidDetail mUidDetail;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mLogicalPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AppDataUsagePreferenceController.class, mLogicalPreferenceGroup);
+        mController = mPreferenceControllerHelper.getController();
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(USER_ID);
+
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowUidDetailProvider.reset();
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void defaultInitialize_hasNoPreference() {
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onDataLoaded_dataNotLoaded_hasNoPreference() {
+        mController.onDataLoaded(null, new int[0]);
+
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onDataLoaded_statsSizeZero_hasNoPreference() {
+        NetworkStats networkStats = new NetworkStats(0, 0);
+
+        mController.onDataLoaded(networkStats, new int[0]);
+
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onDataLoaded_statsLoaded_hasTwoPreference() {
+        NetworkStats networkStats = new NetworkStats(0, 0);
+        NetworkStats.Entry entry1 = new NetworkStats.Entry();
+        entry1.rxBytes = 100;
+        networkStats.addValues(entry1);
+
+        NetworkStats.Entry entry2 = new NetworkStats.Entry();
+        entry2.uid = UID_TETHERING;
+        entry2.rxBytes = 200;
+        networkStats.addValues(entry2);
+
+        mController.onDataLoaded(networkStats, new int[0]);
+
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void onDataLoaded_statsLoaded_hasOnePreference() {
+        ShadowUidDetailProvider.setUidDetail(mUidDetail);
+        NetworkStats networkStats = new NetworkStats(0, 0);
+        NetworkStats.Entry entry1 = new NetworkStats.Entry();
+        entry1.rxBytes = 100;
+        networkStats.addValues(entry1);
+
+        NetworkStats.Entry entry2 = new NetworkStats.Entry();
+        entry2.uid = UID_TETHERING;
+        entry2.rxBytes = 200;
+        networkStats.addValues(entry2);
+
+        mController.onDataLoaded(networkStats, new int[0]);
+
+        ProgressBarPreference preference1 =
+                (ProgressBarPreference) mLogicalPreferenceGroup.getPreference(0);
+        ProgressBarPreference preference2 =
+                (ProgressBarPreference) mLogicalPreferenceGroup.getPreference(1);
+
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+        assertThat(preference1.getProgress()).isEqualTo(100);
+        assertThat(preference2.getProgress()).isEqualTo(50);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/AppsNetworkStatsManagerTest.java b/tests/robotests/src/com/android/car/settings/datausage/AppsNetworkStatsManagerTest.java
new file mode 100644
index 0000000..0b4d124
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/AppsNetworkStatsManagerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkStats;
+import android.os.Bundle;
+
+import androidx.loader.app.LoaderManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowINetworkStatsServiceStub;
+import com.android.car.settings.testutils.ShadowNetworkPolicyManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link AppsNetworkStatsManager}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowINetworkStatsServiceStub.class, ShadowNetworkPolicyManager.class})
+public class AppsNetworkStatsManagerTest {
+
+    private Context mContext;
+    private AppsNetworkStatsManager mAppsNetworkStatsManager;
+
+    @Captor
+    private ArgumentCaptor<LoaderManager.LoaderCallbacks<NetworkStats>>
+            mCallbacksArgumentCaptor;
+
+    @Mock
+    private AppsNetworkStatsManager.Callback mCallback1;
+
+    @Mock
+    private AppsNetworkStatsManager.Callback mCallback2;
+
+    @Mock
+    private LoaderManager mLoaderManager;
+
+    @Mock
+    private INetworkStatsService mINetworkStatsService;
+
+    @Mock
+    private INetworkStatsSession mINetworkStatsSession;
+
+    @Mock
+    private NetworkPolicyManager mNetworkPolicyManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+
+        when(mINetworkStatsService.openSession()).thenReturn(mINetworkStatsSession);
+        ShadowINetworkStatsServiceStub.setINetworkStatsSession(mINetworkStatsService);
+
+        when(mNetworkPolicyManager.getUidsWithPolicy(anyInt())).thenReturn(new int[0]);
+        ShadowNetworkPolicyManager.setNetworkPolicyManager(mNetworkPolicyManager);
+
+        mAppsNetworkStatsManager = new AppsNetworkStatsManager(mContext);
+        mAppsNetworkStatsManager.startLoading(mLoaderManager, Bundle.EMPTY);
+
+        verify(mLoaderManager).restartLoader(eq(1), eq(Bundle.EMPTY),
+                mCallbacksArgumentCaptor.capture());
+    }
+
+    @After
+    public void tearDown() {
+        ShadowINetworkStatsServiceStub.reset();
+        ShadowNetworkPolicyManager.reset();
+    }
+
+    @Test
+    public void callback_onLoadFinished_listenerOnDataLoadedCalled() throws Exception {
+        mAppsNetworkStatsManager.registerListener(mCallback1);
+        mAppsNetworkStatsManager.registerListener(mCallback2);
+
+        NetworkStats networkStats = new NetworkStats(0, 0);
+
+        mCallbacksArgumentCaptor.getValue().onLoadFinished(null, networkStats);
+
+        verify(mCallback1).onDataLoaded(eq(networkStats), any());
+        verify(mCallback2).onDataLoaded(eq(networkStats), any());
+    }
+
+    @Test
+    public void callback_unregisterListener_onlyOneListenerOnDataLoadedCalled() throws Exception {
+        mAppsNetworkStatsManager.registerListener(mCallback1);
+        mAppsNetworkStatsManager.registerListener(mCallback2);
+        mAppsNetworkStatsManager.unregisterListener(mCallback2);
+
+        NetworkStats networkStats = new NetworkStats(0, 0);
+
+        mCallbacksArgumentCaptor.getValue().onLoadFinished(null, networkStats);
+
+        verify(mCallback1).onDataLoaded(eq(networkStats), any());
+        verify(mCallback2, never()).onDataLoaded(eq(networkStats), any());
+    }
+
+    @Test
+    public void callback_notLoaded_listenerOnDataLoadedNotCalled() throws Exception {
+        mAppsNetworkStatsManager.registerListener(mCallback1);
+        mAppsNetworkStatsManager.registerListener(mCallback2);
+        mAppsNetworkStatsManager.unregisterListener(mCallback2);
+
+        NetworkStats networkStats = new NetworkStats(0, 0);
+
+        verify(mCallback1, never()).onDataLoaded(eq(networkStats), any());
+        verify(mCallback2, never()).onDataLoaded(eq(networkStats), any());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/CycleResetDayOfMonthPickerPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datausage/CycleResetDayOfMonthPickerPreferenceControllerTest.java
new file mode 100644
index 0000000..7caec12
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/CycleResetDayOfMonthPickerPreferenceControllerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+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.Context;
+import android.net.NetworkTemplate;
+
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.settingslib.NetworkPolicyEditor;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class CycleResetDayOfMonthPickerPreferenceControllerTest {
+
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<CycleResetDayOfMonthPickerPreferenceController>
+            mControllerHelper;
+    @Mock
+    private NetworkPolicyEditor mPolicyEditor;
+    @Mock
+    private NetworkTemplate mNetworkTemplate;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+
+        mPreference = new Preference(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                CycleResetDayOfMonthPickerPreferenceController.class, mPreference);
+
+        mControllerHelper.getController().setNetworkPolicyEditor(mPolicyEditor);
+        mControllerHelper.getController().setNetworkTemplate(mNetworkTemplate);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void performClick_startsDialogWithStartingValue() {
+        int startingValue = 15;
+        when(mPolicyEditor.getPolicyCycleDay(mNetworkTemplate)).thenReturn(startingValue);
+        mControllerHelper.getController().refreshUi();
+        mPreference.performClick();
+
+        ArgumentCaptor<UsageCycleResetDayOfMonthPickerDialog> dialogCaptor =
+                ArgumentCaptor.forClass(UsageCycleResetDayOfMonthPickerDialog.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                dialogCaptor.capture(), anyString());
+
+        UsageCycleResetDayOfMonthPickerDialog dialog = dialogCaptor.getValue();
+
+        // Dialog was never started because FragmentController is mocked.
+        FragmentController<Fragment> fragmentController = FragmentController.of(new Fragment());
+        Fragment testFragment = fragmentController.setup();
+        dialog.show(testFragment.getFragmentManager(), null);
+
+        assertThat(dialog.getSelectedDayOfMonth()).isEqualTo(startingValue);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/DataLimitPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datausage/DataLimitPreferenceControllerTest.java
new file mode 100644
index 0000000..2106e02
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/DataLimitPreferenceControllerTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.NetworkTemplate;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.NetworkPolicyEditor;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DataLimitPreferenceControllerTest {
+
+    private static final long GIB_IN_BYTES = 1024 * 1024 * 1024;
+    private static final long EPSILON = 100;
+
+    private TwoStatePreference mEnablePreference;
+    private Preference mLimitPreference;
+    private DataLimitPreferenceController mController;
+    private FragmentController mFragmentController;
+    @Mock
+    private NetworkPolicyEditor mPolicyEditor;
+    @Mock
+    private NetworkTemplate mNetworkTemplate;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+
+        PreferenceGroup preferenceGroup = new LogicalPreferenceGroup(context);
+        PreferenceControllerTestHelper<DataLimitPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(context,
+                        DataLimitPreferenceController.class, preferenceGroup);
+        mController = controllerHelper.getController();
+        mFragmentController = controllerHelper.getMockFragmentController();
+
+        mEnablePreference = new SwitchPreference(context);
+        mEnablePreference.setKey(context.getString(R.string.pk_data_set_limit));
+        preferenceGroup.addPreference(mEnablePreference);
+        mLimitPreference = new Preference(context);
+        mLimitPreference.setKey(context.getString(R.string.pk_data_limit));
+        preferenceGroup.addPreference(mLimitPreference);
+
+        mController.setNetworkPolicyEditor(mPolicyEditor);
+        mController.setNetworkTemplate(mNetworkTemplate);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void refreshUi_limitDisabled_summaryEmpty() {
+        when(mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate)).thenReturn(LIMIT_DISABLED);
+        mController.refreshUi();
+
+        assertThat(mLimitPreference.getSummary()).isNull();
+    }
+
+    @Test
+    public void refreshUi_limitDisabled_preferenceDisabled() {
+        when(mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate)).thenReturn(LIMIT_DISABLED);
+        mController.refreshUi();
+
+        assertThat(mLimitPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_limitDisabled_switchUnchecked() {
+        when(mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate)).thenReturn(LIMIT_DISABLED);
+        mController.refreshUi();
+
+        assertThat(mEnablePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_limitEnabled_summaryPopulated() {
+        when(mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate)).thenReturn(5 * GIB_IN_BYTES);
+        mController.refreshUi();
+
+        assertThat(mLimitPreference.getSummary().toString()).isNotEmpty();
+    }
+
+    @Test
+    public void refreshUi_limitEnabled_preferenceEnabled() {
+        when(mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate)).thenReturn(5 * GIB_IN_BYTES);
+        mController.refreshUi();
+
+        assertThat(mLimitPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_limitEnabled_switchChecked() {
+        when(mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate)).thenReturn(5 * GIB_IN_BYTES);
+        mController.refreshUi();
+
+        assertThat(mEnablePreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onPreferenceChanged_toggleFalse_limitBytesDisabled() {
+        mEnablePreference.callChangeListener(false);
+        verify(mPolicyEditor).setPolicyLimitBytes(mNetworkTemplate, LIMIT_DISABLED);
+    }
+
+    @Test
+    public void onPreferenceChanged_toggleTrue_showsDialog() {
+        mEnablePreference.callChangeListener(true);
+
+        verify(mFragmentController).showDialog(any(ConfirmationDialogFragment.class),
+                eq(ConfirmationDialogFragment.TAG));
+    }
+
+    @Test
+    public void onDialogConfirm_noWarningThreshold_setsLimitTo5GB() {
+        mController.onConfirm(null);
+
+        verify(mPolicyEditor).setPolicyLimitBytes(mNetworkTemplate, 5 * GIB_IN_BYTES);
+    }
+
+    @Test
+    public void onDialogConfirm_hasWarningThreshold_setsLimitToWithMultiplier() {
+        when(mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate)).thenReturn(5 * GIB_IN_BYTES);
+        mController.onConfirm(null);
+
+        ArgumentCaptor<Long> setLimit = ArgumentCaptor.forClass(Long.class);
+        verify(mPolicyEditor).setPolicyLimitBytes(eq(mNetworkTemplate), setLimit.capture());
+
+        long setValue = setLimit.getValue();
+        // Due to precision errors, add and subtract a small epsilon.
+        assertThat(setValue).isGreaterThan(
+                (long) (5 * GIB_IN_BYTES * DataLimitPreferenceController.LIMIT_BYTES_MULTIPLIER)
+                        - EPSILON);
+        assertThat(setValue).isLessThan(
+                (long) (5 * GIB_IN_BYTES * DataLimitPreferenceController.LIMIT_BYTES_MULTIPLIER)
+                        + EPSILON);
+    }
+
+    @Test
+    public void onPreferenceClicked_showsPickerDialog() {
+        mLimitPreference.performClick();
+
+        verify(mFragmentController).showDialog(any(UsageBytesThresholdPickerDialog.class),
+                eq(UsageBytesThresholdPickerDialog.TAG));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/DataUsageEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datausage/DataUsageEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..79187d5
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/DataUsageEntryPreferenceControllerTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.shadow.api.Shadow.extract;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
+import android.util.RecurrenceRule;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowConnectivityManager;
+import com.android.car.settings.testutils.ShadowSubscriptionManager;
+
+import com.google.android.collect.Lists;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowNetwork;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowConnectivityManager.class, ShadowSubscriptionManager.class})
+public class DataUsageEntryPreferenceControllerTest {
+
+    private static final int SUBSCRIPTION_ID = 1;
+
+    private Context mContext;
+    private Preference mPreference;
+    private DataUsageEntryPreferenceController mController;
+    @Mock
+    private NetworkCapabilities mNetworkCapabilities;
+    @Mock
+    private SubscriptionPlan mSubscriptionPlan;
+    @Mock
+    private RecurrenceRule mRecurrenceRule;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        PreferenceControllerTestHelper<DataUsageEntryPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        DataUsageEntryPreferenceController.class, mPreference);
+        mController = controllerHelper.getController();
+
+        // Setup to always make preference available.
+        getShadowConnectivityManager().clearAllNetworks();
+        getShadowConnectivityManager().addNetworkCapabilities(
+                ShadowNetwork.newInstance(ConnectivityManager.TYPE_MOBILE), mNetworkCapabilities);
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(
+                true);
+
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowConnectivityManager.reset();
+        ShadowSubscriptionManager.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_noMobileNetwork_isUnsupported() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(
+                false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hasMobileNetwork_isAvailable() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(
+                true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void refreshUi_noDefaultSubscriptionId_noSummarySet() {
+        ShadowSubscriptionManager.setDefaultSubscriptionId(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        mController.refreshUi();
+        assertThat(mPreference.getSummary()).isNull();
+    }
+
+    @Test
+    public void refreshUi_noPrimaryPlan_noSummarySet() {
+        ShadowSubscriptionManager.setDefaultSubscriptionId(SUBSCRIPTION_ID);
+        getShadowSubscriptionManager().setSubscriptionPlans(Lists.newArrayList());
+
+        mController.refreshUi();
+        assertThat(mPreference.getSummary()).isNull();
+    }
+
+    @Test
+    public void refreshUi_hasPrimaryPlan_setsSummary() {
+        ShadowSubscriptionManager.setDefaultSubscriptionId(SUBSCRIPTION_ID);
+        getShadowSubscriptionManager().setSubscriptionPlans(Lists.newArrayList(mSubscriptionPlan));
+        when(mSubscriptionPlan.getDataLimitBytes()).thenReturn(100L);
+        when(mSubscriptionPlan.getDataUsageBytes()).thenReturn(10L);
+        when(mSubscriptionPlan.getCycleRule()).thenReturn(mRecurrenceRule);
+
+        mController.refreshUi();
+        assertThat(mPreference.getSummary().length()).isGreaterThan(0);
+    }
+
+    private ShadowConnectivityManager getShadowConnectivityManager() {
+        return (ShadowConnectivityManager) extract(
+                mContext.getSystemService(ConnectivityManager.class));
+    }
+
+    private ShadowSubscriptionManager getShadowSubscriptionManager() {
+        return (ShadowSubscriptionManager) extract(
+                mContext.getSystemService(SubscriptionManager.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceControllerTest.java
new file mode 100644
index 0000000..1fb0c60
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceControllerTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.robolectric.shadow.api.Shadow.extract;
+
+import android.content.Context;
+import android.net.NetworkTemplate;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.format.Formatter;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowDataUsageController;
+import com.android.car.settings.testutils.ShadowSubscriptionManager;
+import com.android.car.settings.testutils.ShadowTelephonyManager;
+import com.android.settingslib.net.DataUsageController;
+
+import com.google.android.collect.Lists;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.time.Period;
+import java.time.ZonedDateTime;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowTelephonyManager.class, ShadowSubscriptionManager.class,
+        ShadowDataUsageController.class})
+public class DataUsageSummaryPreferenceControllerTest {
+
+    private static final CharSequence TEST_CARRIER_NAME = "TEST_CARRIER_NAME";
+
+    private Context mContext;
+    private DataUsageSummaryPreference mDataUsageSummaryPreference;
+    private DataUsageSummaryPreferenceController mController;
+    private PreferenceControllerTestHelper<DataUsageSummaryPreferenceController> mControllerHelper;
+    private NetworkTemplate mNetworkTemplate;
+    @Mock
+    private DataUsageController mDataUsageController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        SubscriptionInfo info = mock(SubscriptionInfo.class);
+        when(info.getSubscriptionId()).thenReturn(1);
+        ShadowSubscriptionManager.setDefaultDataSubscriptionInfo(info);
+        ShadowDataUsageController.setInstance(mDataUsageController);
+
+        mContext = RuntimeEnvironment.application;
+        mDataUsageSummaryPreference = new DataUsageSummaryPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DataUsageSummaryPreferenceController.class, mDataUsageSummaryPreference);
+        mController = mControllerHelper.getController();
+
+        mNetworkTemplate = DataUsageUtils.getMobileNetworkTemplate(
+                mContext.getSystemService(TelephonyManager.class),
+                DataUsageUtils.getDefaultSubscriptionId(
+                        mContext.getSystemService(SubscriptionManager.class)));
+    }
+
+    @After
+    public void tearDown() {
+        ShadowTelephonyManager.reset();
+        ShadowSubscriptionManager.reset();
+        ShadowDataUsageController.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_hasSim_isAvailable() {
+        getShadowTelephonyManager().setSimState(TelephonyManager.SIM_STATE_LOADED);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noSim_isConditionallyUnavailable() {
+        getShadowTelephonyManager().setSimState(TelephonyManager.SIM_STATE_UNKNOWN);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void refreshUi_hasUsage_titleSet() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        String usedValueString = Formatter.formatBytes(mContext.getResources(), info.usageLevel,
+                Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS).value;
+        assertThat(mDataUsageSummaryPreference.getTitle().toString()).contains(usedValueString);
+    }
+
+    @Test
+    public void refreshUi_noLimits_doesntSetDataLimitText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        info.limitLevel = -1;
+        info.warningLevel = -1;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getDataLimitText()).isNull();
+    }
+
+    @Test
+    public void refreshUi_hasLimit_setsDataLimitText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        info.limitLevel = 100000;
+        info.warningLevel = -1;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getDataLimitText().toString()).isEqualTo(
+                TextUtils.expandTemplate(mContext.getText(R.string.cell_data_limit),
+                        DataUsageUtils.bytesToIecUnits(mContext, info.limitLevel)).toString());
+    }
+
+    @Test
+    public void refreshUi_hasWarning_setsDataLimitText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        info.limitLevel = -1;
+        info.warningLevel = 50000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getDataLimitText().toString()).isEqualTo(
+                TextUtils.expandTemplate(mContext.getText(R.string.cell_data_warning),
+                        DataUsageUtils.bytesToIecUnits(mContext, info.warningLevel)).toString());
+    }
+
+    @Test
+    public void refreshUi_hasWarningAndLimit_setsDataLimitText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        info.limitLevel = 100000;
+        info.warningLevel = 50000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getDataLimitText().toString()).isEqualTo(
+                TextUtils.expandTemplate(mContext.getText(R.string.cell_data_warning_and_limit),
+                        DataUsageUtils.bytesToIecUnits(mContext, info.warningLevel),
+                        DataUsageUtils.bytesToIecUnits(mContext, info.limitLevel)).toString());
+    }
+
+    @Test
+    public void refreshUi_endTimeIsGreaterThanOneDay_setsBillingCycleText() {
+        int numDays = 20;
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        // Add an extra hour to account for the difference in time when the test calls
+        // System.currentTimeMillis() vs when the code calls it.
+        info.cycleEnd = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(numDays)
+                + TimeUnit.HOURS.toMillis(1);
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getRemainingBillingCycleText().toString())
+                .isEqualTo(
+                        mContext.getResources().getQuantityString(R.plurals.billing_cycle_days_left,
+                                numDays, numDays));
+    }
+
+    @Test
+    public void refreshUi_endTimeIsLessThanOneDay_setsBillingCycleText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.cycleEnd = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(22);
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getRemainingBillingCycleText().toString())
+                .isEqualTo(
+                        mContext.getString(R.string.billing_cycle_less_than_one_day_left));
+    }
+
+    @Test
+    public void refreshUi_endTimeIsNow_setsBillingCycleText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.cycleEnd = System.currentTimeMillis();
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getRemainingBillingCycleText().toString())
+                .isEqualTo(
+                        mContext.getString(R.string.billing_cycle_none_left));
+    }
+
+    @Test
+    public void refreshUi_hasCarrierName_hasRecentUpdate_setsCarrierInfoText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        setCarrierName(TEST_CARRIER_NAME);
+        setSubscriptionPlan(/* usageBytes= */ 1000L, System.currentTimeMillis());
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getCarrierInfoText()).isEqualTo(
+                TextUtils.expandTemplate(mContext.getText(R.string.carrier_and_update_now_text),
+                        TEST_CARRIER_NAME));
+    }
+
+    @Test
+    public void refreshUi_hasCarrierName_hasOldUpdate_setsCarrierInfoText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        int numDays = 15;
+        setCarrierName(TEST_CARRIER_NAME);
+        setSubscriptionPlan(/* usageBytes= */ 1000L,
+                System.currentTimeMillis() - TimeUnit.DAYS.toMillis(numDays));
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getCarrierInfoText()).isEqualTo(
+                TextUtils.expandTemplate(mContext.getText(R.string.carrier_and_update_text),
+                        TEST_CARRIER_NAME, numDays + " days"));
+    }
+
+    @Test
+    public void refreshUi_noCarrierName_hasRecentUpdate_setsCarrierInfoText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        setSubscriptionPlan(/* usageBytes= */ 1000L, System.currentTimeMillis());
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getCarrierInfoText().toString()).isEqualTo(
+                mContext.getString(R.string.no_carrier_update_now_text));
+    }
+
+    @Test
+    public void refreshUi_noCarrierName_hasOldUpdate_setsCarrierInfoText() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        int numDays = 15;
+        setSubscriptionPlan(/* usageBytes= */ 1000L,
+                System.currentTimeMillis() - TimeUnit.DAYS.toMillis(numDays));
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getCarrierInfoText()).isEqualTo(
+                TextUtils.expandTemplate(mContext.getText(R.string.no_carrier_update_text),
+                        null, numDays + " days"));
+    }
+
+    @Test
+    public void refreshUi_hasUpdateTimeOlderThanWarning_setsCarrierInfoStyle() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+
+        // Subtract an extra hour to account fo the difference in calls to
+        // System.currentTimeMillis().
+        setSubscriptionPlan(/* usageBytes= */ 1000L,
+                System.currentTimeMillis() - DataUsageSummaryPreferenceController.WARNING_AGE
+                        - TimeUnit.HOURS.toMillis(1));
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getCarrierInfoTextStyle()).isEqualTo(
+                R.style.DataUsageSummaryCarrierInfoWarningTextAppearance);
+    }
+
+    @Test
+    public void refreshUi_hasUpdateTimeYoungerThanWarning_setsCarrierInfoStyle() {
+        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
+        info.usageLevel = 10000;
+        when(mDataUsageController.getDataUsageInfo(mNetworkTemplate)).thenReturn(info);
+
+        // Subtract an extra hour to account fo the difference in calls to
+        // System.currentTimeMillis().
+        setSubscriptionPlan(/* usageBytes= */ 1000L,
+                System.currentTimeMillis() - DataUsageSummaryPreferenceController.WARNING_AGE
+                        + TimeUnit.HOURS.toMillis(1));
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mDataUsageSummaryPreference.getCarrierInfoTextStyle()).isEqualTo(
+                R.style.DataUsageSummaryCarrierInfoTextAppearance);
+    }
+
+    private ShadowTelephonyManager getShadowTelephonyManager() {
+        return (ShadowTelephonyManager) extract(
+                mContext.getSystemService(TelephonyManager.class));
+    }
+
+    private ShadowSubscriptionManager getShadowSubscriptionManager() {
+        return Shadow.extract(mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE));
+    }
+
+    private void setCarrierName(CharSequence name) {
+        SubscriptionInfo subInfo = new SubscriptionInfo(/* id= */ 0, /* iccId= */ "",
+                /* simSlotIndex= */ 0, /* displayName= */ "", name,
+                /* nameSource= */ 0, /* iconTint= */ 0, /* number= */ "",
+                /* roaming= */ 0, /* icon= */ null, /* mcc= */ 0, /* mnc= */ 0,
+                /* countryIso= */ "");
+        ShadowSubscriptionManager.setDefaultDataSubscriptionInfo(subInfo);
+    }
+
+    private void setSubscriptionPlan(long usageBytes, long snapshotMillis) {
+        ZonedDateTime start = ZonedDateTime.now();
+        ZonedDateTime end = ZonedDateTime.now().plusDays(30);
+        SubscriptionPlan plan = new SubscriptionPlan.Builder(start, end, Period.ofMonths(1))
+                .setDataLimit(/* dataLimitBytes= */ 5000000000L,
+                        SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
+                .setDataUsage(usageBytes, snapshotMillis)
+                .build();
+        getShadowSubscriptionManager().setSubscriptionPlans(Lists.newArrayList(plan));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceTest.java b/tests/robotests/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceTest.java
new file mode 100644
index 0000000..560614a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/DataUsageSummaryPreferenceTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DataUsageSummaryPreferenceTest {
+    private static final String TEST_LABEL = "TEST_LABEL";
+    private static final Intent TEST_INTENT = new Intent("test_action");
+
+    private Context mContext;
+    private PreferenceViewHolder mViewHolder;
+    private DataUsageSummaryPreference mDataUsageSummaryPreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        View rootView = View.inflate(mContext, R.layout.data_usage_summary_preference,
+                /* root= */ null);
+        mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+        mDataUsageSummaryPreference = new DataUsageSummaryPreference(mContext);
+    }
+
+    @Test
+    public void onBindViewHolder_noDataUsageText_isGone() {
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getDataUsageText().getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void onBindViewHolder_hasDataUsageText_isVisible() {
+        mDataUsageSummaryPreference.setDataLimitText(TEST_LABEL);
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getDataUsageText().getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onBindViewHolder_hasDataUsageText_setsText() {
+        mDataUsageSummaryPreference.setDataLimitText(TEST_LABEL);
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mDataUsageSummaryPreference.getDataLimitText().toString()).isEqualTo(TEST_LABEL);
+    }
+
+    @Test
+    public void onBindViewHolder_noRemainingBillingCycleText_isGone() {
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getRemainingBillingCycleText().getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void onBindViewHolder_hasRemainingBillingCycleText_isVisible() {
+        mDataUsageSummaryPreference.setRemainingBillingCycleText(TEST_LABEL);
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getRemainingBillingCycleText().getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onBindViewHolder_hasRemainingBillingCycleText_setsText() {
+        mDataUsageSummaryPreference.setRemainingBillingCycleText(TEST_LABEL);
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mDataUsageSummaryPreference.getRemainingBillingCycleText().toString()).isEqualTo(
+                TEST_LABEL);
+    }
+
+    @Test
+    public void onBindViewHolder_noCarrierInfoText_isGone() {
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getCarrierInfoText().getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void onBindViewHolder_hasCarrierInfoText_isVisible() {
+        mDataUsageSummaryPreference.setCarrierInfoText(TEST_LABEL);
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getCarrierInfoText().getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onBindViewHolder_hasCarrierInfoText_setsText() {
+        mDataUsageSummaryPreference.setCarrierInfoText(TEST_LABEL);
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mDataUsageSummaryPreference.getCarrierInfoText().toString()).isEqualTo(
+                TEST_LABEL);
+    }
+
+    @Test
+    public void onBindViewHolder_noManagePlanIntent_isGone() {
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getManageSubscriptionButton().getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void onBindViewHolder_hasManagePlanIntent_isVisible() {
+        mDataUsageSummaryPreference.setManageSubscriptionIntent(TEST_INTENT);
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(getManageSubscriptionButton().getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onButtonClick_hasManagePlanIntent_startsActivity() {
+        mDataUsageSummaryPreference.setManageSubscriptionIntent(TEST_INTENT);
+        mDataUsageSummaryPreference.onBindViewHolder(mViewHolder);
+        getManageSubscriptionButton().performClick();
+
+        Intent actual = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(actual.getAction()).isEqualTo(TEST_INTENT.getAction());
+    }
+
+    private TextView getDataUsageText() {
+        return (TextView) mViewHolder.findViewById(R.id.data_limit_text);
+    }
+
+    private TextView getRemainingBillingCycleText() {
+        return (TextView) mViewHolder.findViewById(R.id.remaining_billing_cycle_time_text);
+    }
+
+    private TextView getCarrierInfoText() {
+        return (TextView) mViewHolder.findViewById(R.id.carrier_info_text);
+    }
+
+    private Button getManageSubscriptionButton() {
+        return (Button) mViewHolder.findViewById(R.id.manage_subscription_button);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/DataUsageUtilsTest.java b/tests/robotests/src/com/android/car/settings/datausage/DataUsageUtilsTest.java
new file mode 100644
index 0000000..57a083a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/DataUsageUtilsTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
+import android.util.RecurrenceRule;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DataUsageUtilsTest {
+
+    private static final int SUBSCRIPTION_ID = 1;
+
+    @Test
+    public void getDefaultSubscriptionId_nullSubscriptionManager_returnsInvalidId() {
+        assertThat(DataUsageUtils.getDefaultSubscriptionId(null)).isEqualTo(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+    }
+
+    @Test
+    public void getDefaultSubscriptionId_noSubscriptions_returnsInvalidId() {
+        SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
+        when(subscriptionManager.getDefaultDataSubscriptionInfo()).thenReturn(null);
+        when(subscriptionManager.getAllSubscriptionInfoList()).thenReturn(Collections.emptyList());
+
+        assertThat(DataUsageUtils.getDefaultSubscriptionId(subscriptionManager)).isEqualTo(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+    }
+
+    @Test
+    public void getDefaultSubscriptionId_noDefaultSubscription_returnsFirstSubscription() {
+        SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
+        when(subscriptionManager.getDefaultDataSubscriptionInfo()).thenReturn(null);
+
+        SubscriptionInfo info1 = mock(SubscriptionInfo.class);
+        SubscriptionInfo info2 = mock(SubscriptionInfo.class);
+        when(info1.getSubscriptionId()).thenReturn(1);
+        when(info2.getSubscriptionId()).thenReturn(2);
+        when(subscriptionManager.getAllSubscriptionInfoList()).thenReturn(
+                Lists.newArrayList(info1, info2));
+
+        assertThat(DataUsageUtils.getDefaultSubscriptionId(subscriptionManager)).isEqualTo(1);
+    }
+
+    @Test
+    public void getDefaultSubscriptionId_hasDefaultSubscription_returnsDefaultSubscription() {
+        SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
+        SubscriptionInfo info = mock(SubscriptionInfo.class);
+        when(info.getSubscriptionId()).thenReturn(1);
+        when(subscriptionManager.getDefaultDataSubscriptionInfo()).thenReturn(info);
+
+        assertThat(DataUsageUtils.getDefaultSubscriptionId(subscriptionManager)).isEqualTo(1);
+    }
+
+    @Test
+    public void getPrimaryPlan_noSubscriptions_returnsNull() {
+        SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
+        when(subscriptionManager.getSubscriptionPlans(SUBSCRIPTION_ID)).thenReturn(
+                Lists.newArrayList());
+
+        assertThat(DataUsageUtils.getPrimaryPlan(subscriptionManager, SUBSCRIPTION_ID)).isNull();
+    }
+
+    @Test
+    public void getPrimaryPlan_dataLimitBytesIsZero_returnsNull() {
+        SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
+        SubscriptionPlan subscriptionPlan = mock(SubscriptionPlan.class);
+        when(subscriptionManager.getSubscriptionPlans(SUBSCRIPTION_ID)).thenReturn(
+                Lists.newArrayList(subscriptionPlan));
+        when(subscriptionPlan.getDataLimitBytes()).thenReturn(0L);
+
+        assertThat(DataUsageUtils.getPrimaryPlan(subscriptionManager, SUBSCRIPTION_ID)).isNull();
+
+    }
+
+    @Test
+    public void getPrimaryPlan_dataUsageBytesIsHuge_returnsNull() {
+        SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
+        SubscriptionPlan subscriptionPlan = mock(SubscriptionPlan.class);
+        when(subscriptionManager.getSubscriptionPlans(SUBSCRIPTION_ID)).thenReturn(
+                Lists.newArrayList(subscriptionPlan));
+        when(subscriptionPlan.getDataLimitBytes()).thenReturn(100L);
+        when(subscriptionPlan.getDataUsageBytes()).thenReturn(2 * DataUsageUtils.PETA);
+
+        assertThat(DataUsageUtils.getPrimaryPlan(subscriptionManager, SUBSCRIPTION_ID)).isNull();
+    }
+
+    @Test
+    public void getPrimaryPlan_cycleRuleIsNull_returnsNull() {
+        SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
+        SubscriptionPlan subscriptionPlan = mock(SubscriptionPlan.class);
+        when(subscriptionManager.getSubscriptionPlans(SUBSCRIPTION_ID)).thenReturn(
+                Lists.newArrayList(subscriptionPlan));
+        when(subscriptionPlan.getDataLimitBytes()).thenReturn(100L);
+        when(subscriptionPlan.getDataUsageBytes()).thenReturn(10L);
+        when(subscriptionPlan.getCycleRule()).thenReturn(null);
+
+        assertThat(DataUsageUtils.getPrimaryPlan(subscriptionManager, SUBSCRIPTION_ID)).isNull();
+    }
+
+    @Test
+    public void getPrimaryPlan_cycleRuleIsValid_returnsSubscriptionPlan() {
+        SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
+        SubscriptionPlan subscriptionPlan = mock(SubscriptionPlan.class);
+        RecurrenceRule recurrenceRule = mock(RecurrenceRule.class);
+        when(subscriptionManager.getSubscriptionPlans(SUBSCRIPTION_ID)).thenReturn(
+                Lists.newArrayList(subscriptionPlan));
+        when(subscriptionPlan.getDataLimitBytes()).thenReturn(100L);
+        when(subscriptionPlan.getDataUsageBytes()).thenReturn(10L);
+        when(subscriptionPlan.getCycleRule()).thenReturn(recurrenceRule);
+
+        assertThat(DataUsageUtils.getPrimaryPlan(subscriptionManager, SUBSCRIPTION_ID)).isEqualTo(
+                subscriptionPlan);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/DataWarningPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datausage/DataWarningPreferenceControllerTest.java
new file mode 100644
index 0000000..244c557
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/DataWarningPreferenceControllerTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static android.net.NetworkPolicy.WARNING_DISABLED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.NetworkTemplate;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.NetworkPolicyEditor;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DataWarningPreferenceControllerTest {
+
+    private static final long BYTES_IN_GIGABYTE = 1024 * 1024 * 1024;
+
+    private TwoStatePreference mEnablePreference;
+    private Preference mWarningPreference;
+    private DataWarningPreferenceController mController;
+    private FragmentController mFragmentController;
+    @Mock
+    private NetworkPolicyEditor mPolicyEditor;
+    @Mock
+    private NetworkTemplate mNetworkTemplate;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+
+        PreferenceGroup preferenceGroup = new LogicalPreferenceGroup(context);
+        PreferenceControllerTestHelper<DataWarningPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(context,
+                        DataWarningPreferenceController.class, preferenceGroup);
+        mController = controllerHelper.getController();
+        mFragmentController = controllerHelper.getMockFragmentController();
+
+        mEnablePreference = new SwitchPreference(context);
+        mEnablePreference.setKey(context.getString(R.string.pk_data_set_warning));
+        preferenceGroup.addPreference(mEnablePreference);
+        mWarningPreference = new Preference(context);
+        mWarningPreference.setKey(context.getString(R.string.pk_data_warning));
+        preferenceGroup.addPreference(mWarningPreference);
+
+        mController.setNetworkPolicyEditor(mPolicyEditor);
+        mController.setNetworkTemplate(mNetworkTemplate);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void refreshUi_warningDisabled_summaryEmpty() {
+        when(mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate)).thenReturn(WARNING_DISABLED);
+        mController.refreshUi();
+
+        assertThat(mWarningPreference.getSummary()).isNull();
+    }
+
+    @Test
+    public void refreshUi_warningDisabled_preferenceDisabled() {
+        when(mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate)).thenReturn(WARNING_DISABLED);
+        mController.refreshUi();
+
+        assertThat(mWarningPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_warningDisabled_switchUnchecked() {
+        when(mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate)).thenReturn(WARNING_DISABLED);
+        mController.refreshUi();
+
+        assertThat(mEnablePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_warningEnabled_summaryPopulated() {
+        when(mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate)).thenReturn(
+                3 * BYTES_IN_GIGABYTE);
+        mController.refreshUi();
+
+        assertThat(mWarningPreference.getSummary().toString()).isNotEmpty();
+    }
+
+    @Test
+    public void refreshUi_warningEnabled_preferenceEnabled() {
+        when(mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate)).thenReturn(
+                3 * BYTES_IN_GIGABYTE);
+        mController.refreshUi();
+
+        assertThat(mWarningPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_warningEnabled_switchChecked() {
+        when(mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate)).thenReturn(
+                3 * BYTES_IN_GIGABYTE);
+        mController.refreshUi();
+
+        assertThat(mEnablePreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onPreferenceChanged_toggleFalse_warningBytesDisabled() {
+        mEnablePreference.callChangeListener(false);
+        verify(mPolicyEditor).setPolicyWarningBytes(mNetworkTemplate, WARNING_DISABLED);
+    }
+
+    @Test
+    public void onPreferenceChanged_toggleTrue_warningBytesNotDisabled() {
+        mEnablePreference.callChangeListener(true);
+
+        ArgumentCaptor<Long> setWarning = ArgumentCaptor.forClass(Long.class);
+        verify(mPolicyEditor).setPolicyWarningBytes(eq(mNetworkTemplate), setWarning.capture());
+        assertThat(setWarning.getValue()).isNotEqualTo(WARNING_DISABLED);
+    }
+
+    @Test
+    public void onPreferenceClicked_showsPickerDialog() {
+        mWarningPreference.performClick();
+
+        verify(mFragmentController).showDialog(any(UsageBytesThresholdPickerDialog.class),
+                eq(UsageBytesThresholdPickerDialog.TAG));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/UsageBytesThresholdPickerDialogTest.java b/tests/robotests/src/com/android/car/settings/datausage/UsageBytesThresholdPickerDialogTest.java
new file mode 100644
index 0000000..52f05ec
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/UsageBytesThresholdPickerDialogTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.shadows.ShadowDialog;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class UsageBytesThresholdPickerDialogTest {
+
+    private Fragment mFragment;
+    @Mock
+    private UsageBytesThresholdPickerDialog.BytesThresholdPickedListener
+            mBytesThresholdPickedListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mFragment = FragmentController.of(new Fragment()).setup();
+    }
+
+    @Test
+    public void dialogInit_validValue_showsCurrentValue() {
+        long twoGB = 2 * UsageBytesThresholdPickerDialog.GIB_IN_BYTES;
+        UsageBytesThresholdPickerDialog dialog = UsageBytesThresholdPickerDialog.newInstance(
+                R.string.data_usage_limit_editor_title, twoGB);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+        assertThat(dialog.getCurrentThreshold()).isEqualTo(twoGB);
+    }
+
+    @Test
+    public void dialogInit_lowInvalidValue_showsLowestPossibleValue() {
+        UsageBytesThresholdPickerDialog dialog = UsageBytesThresholdPickerDialog.newInstance(
+                R.string.data_usage_limit_editor_title, -1);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+        assertThat(dialog.getCurrentThreshold()).isEqualTo(0);
+    }
+
+    @Test
+    public void positiveButtonClick_noChangeInValue_dialogListenerNotCalled() {
+        long twoGB = 2 * UsageBytesThresholdPickerDialog.GIB_IN_BYTES;
+        UsageBytesThresholdPickerDialog dialog = UsageBytesThresholdPickerDialog.newInstance(
+                R.string.data_usage_limit_editor_title, twoGB);
+        dialog.setBytesThresholdPickedListener(mBytesThresholdPickedListener);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+
+        AlertDialog alertDialog = (AlertDialog) ShadowDialog.getLatestDialog();
+        alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        verify(mBytesThresholdPickedListener, never()).onThresholdPicked(anyLong());
+    }
+
+    @Test
+    public void positiveButtonClick_changeInValue_dialogListenerCalled() {
+        long twoGB = 2 * UsageBytesThresholdPickerDialog.GIB_IN_BYTES;
+        UsageBytesThresholdPickerDialog dialog = UsageBytesThresholdPickerDialog.newInstance(
+                R.string.data_usage_limit_editor_title, twoGB);
+        dialog.setBytesThresholdPickedListener(mBytesThresholdPickedListener);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+
+        long threeGB = 3 * UsageBytesThresholdPickerDialog.GIB_IN_BYTES;
+        dialog.setThresholdEditor(threeGB);
+
+        AlertDialog alertDialog = (AlertDialog) ShadowDialog.getLatestDialog();
+        alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        verify(mBytesThresholdPickedListener).onThresholdPicked(threeGB);
+    }
+
+    @Test
+    public void getCurrentThreshold_aboveLimit_returnLimit() {
+        long limitGBTimesTwo = 2 * UsageBytesThresholdPickerDialog.MAX_DATA_LIMIT_BYTES;
+        UsageBytesThresholdPickerDialog dialog = UsageBytesThresholdPickerDialog.newInstance(
+                R.string.data_usage_limit_editor_title, limitGBTimesTwo);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+
+        assertThat(dialog.getCurrentThreshold()).isEqualTo(
+                UsageBytesThresholdPickerDialog.MAX_DATA_LIMIT_BYTES);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/UsageCycleResetDayOfMonthPickerDialogTest.java b/tests/robotests/src/com/android/car/settings/datausage/UsageCycleResetDayOfMonthPickerDialogTest.java
new file mode 100644
index 0000000..14b741a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/UsageCycleResetDayOfMonthPickerDialogTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.datausage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.FragmentController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.shadows.ShadowDialog;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class UsageCycleResetDayOfMonthPickerDialogTest {
+
+    private Fragment mFragment;
+    @Mock
+    private UsageCycleResetDayOfMonthPickerDialog.ResetDayOfMonthPickedListener
+            mResetDayOfMonthPickedListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mFragment = FragmentController.of(new Fragment()).setup();
+    }
+
+    @Test
+    public void dialogInit_validValue_showsCurrentValue() {
+        int setDate = 15;
+        UsageCycleResetDayOfMonthPickerDialog dialog =
+                UsageCycleResetDayOfMonthPickerDialog.newInstance(setDate);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+        assertThat(dialog.getSelectedDayOfMonth()).isEqualTo(setDate);
+    }
+
+    @Test
+    public void dialogInit_lowInvalidValue_showsLowestPossibleValue() {
+        UsageCycleResetDayOfMonthPickerDialog dialog =
+                UsageCycleResetDayOfMonthPickerDialog.newInstance(0);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+        assertThat(dialog.getSelectedDayOfMonth()).isEqualTo(1);
+    }
+
+    @Test
+    public void dialogInit_highInvalidValue_showsHighestPossibleValue() {
+        UsageCycleResetDayOfMonthPickerDialog dialog =
+                UsageCycleResetDayOfMonthPickerDialog.newInstance(32);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+        assertThat(dialog.getSelectedDayOfMonth()).isEqualTo(31);
+    }
+
+    @Test
+    public void dialogListenerCalled() {
+        int setDate = 15;
+        UsageCycleResetDayOfMonthPickerDialog dialog =
+                UsageCycleResetDayOfMonthPickerDialog.newInstance(setDate);
+        dialog.setResetDayOfMonthPickedListener(mResetDayOfMonthPickedListener);
+        dialog.show(mFragment.getFragmentManager(), /* tag= */ null);
+
+        AlertDialog alertDialog = (AlertDialog) ShadowDialog.getLatestDialog();
+        alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        verify(mResetDayOfMonthPickedListener).onDayOfMonthPicked(setDate);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datetime/AutoDatetimeTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datetime/AutoDatetimeTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..62b74a6
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datetime/AutoDatetimeTogglePreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AutoDatetimeTogglePreferenceControllerTest {
+
+    private Context mContext;
+    private SwitchPreference mPreference;
+    private PreferenceControllerTestHelper<AutoDatetimeTogglePreferenceController>
+            mPreferenceControllerHelper;
+    private AutoDatetimeTogglePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AutoDatetimeTogglePreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void testRefreshUi_unchecked() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0);
+        mController.refreshUi();
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_checked() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 1);
+        mController.refreshUi();
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void testOnPreferenceChange_autoTimeZoneSet_shouldSendIntent() {
+        mPreference.setChecked(true);
+        mPreference.callChangeListener(true);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired.size()).isEqualTo(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
+    }
+
+    @Test
+    public void testOnPreferenceChange_autoTimeZoneUnset_shouldSendIntent() {
+        mPreference.setChecked(false);
+        mPreference.callChangeListener(false);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired.size()).isEqualTo(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datetime/AutoTimeZoneTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datetime/AutoTimeZoneTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..cd0464f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datetime/AutoTimeZoneTogglePreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AutoTimeZoneTogglePreferenceControllerTest {
+
+    private Context mContext;
+    private SwitchPreference mPreference;
+    private PreferenceControllerTestHelper<AutoTimeZoneTogglePreferenceController>
+            mPreferenceControllerHelper;
+    private AutoTimeZoneTogglePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AutoTimeZoneTogglePreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void testRefreshUi_unchecked() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, 0);
+        mController.refreshUi();
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_checked() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, 1);
+        mController.refreshUi();
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void testOnPreferenceChange_autoTimeZoneSet_shouldSendIntent() {
+        mPreference.setChecked(true);
+        mController.handlePreferenceChanged(mPreference, true);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired.size()).isEqualTo(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
+    }
+
+    @Test
+    public void testOnPreferenceChange_autoTimeZoneUnset_shouldSendIntent() {
+        mPreference.setChecked(false);
+        mController.handlePreferenceChanged(mPreference, false);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired.size()).isEqualTo(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datetime/DatePickerPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datetime/DatePickerPreferenceControllerTest.java
new file mode 100644
index 0000000..463c83a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datetime/DatePickerPreferenceControllerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class DatePickerPreferenceControllerTest {
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<DatePickerPreferenceController>
+            mPreferenceControllerHelper;
+    private DatePickerPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                DatePickerPreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void testRefreshUi_disabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 1);
+        mController.refreshUi();
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_enabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0);
+        mController.refreshUi();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void testRefreshUi_fromBroadcastReceiver_disabled() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.STARTED);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 1);
+        mContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_fromBroadcastReceiver_enabled() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.STARTED);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0);
+        mContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datetime/TimeFormatTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datetime/TimeFormatTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..b89eee6
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datetime/TimeFormatTogglePreferenceControllerTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class TimeFormatTogglePreferenceControllerTest {
+
+    private Context mContext;
+    private TwoStatePreference mPreference;
+    private PreferenceControllerTestHelper<TimeFormatTogglePreferenceController>
+            mPreferenceControllerHelper;
+    private TimeFormatTogglePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TimeFormatTogglePreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void testRefreshUi_24HourSet_shouldCheckPreference() {
+        Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
+                TimeFormatTogglePreferenceController.HOURS_24);
+        mController.refreshUi();
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void testRefreshUi_12HourSet_shouldUncheckPreference() {
+        Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
+                TimeFormatTogglePreferenceController.HOURS_12);
+        mController.refreshUi();
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void testOnPreferenceChange_24HourSet_shouldSendIntent() {
+        mPreference.setChecked(true);
+        mPreference.callChangeListener(true);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired.size()).isEqualTo(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
+        assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
+                .isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR);
+    }
+
+    @Test
+    public void testOnPreferenceChange_12HourSet_shouldSendIntent() {
+        mPreference.setChecked(false);
+        mPreference.callChangeListener(false);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired.size()).isEqualTo(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
+        assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
+                .isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datetime/TimePickerPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datetime/TimePickerPreferenceControllerTest.java
new file mode 100644
index 0000000..accc5e1
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datetime/TimePickerPreferenceControllerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class TimePickerPreferenceControllerTest {
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<TimePickerPreferenceController>
+            mPreferenceControllerHelper;
+    private TimePickerPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TimePickerPreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void testRefreshUi_disabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 1);
+        mController.refreshUi();
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_enabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0);
+        mController.refreshUi();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void testRefreshUi_fromBroadcastReceiver_disabled() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.STARTED);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 1);
+        mContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_fromBroadcastReceiver_enabled() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.STARTED);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0);
+        mContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datetime/TimeZonePickerPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datetime/TimeZonePickerPreferenceControllerTest.java
new file mode 100644
index 0000000..ed84a8c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datetime/TimeZonePickerPreferenceControllerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class TimeZonePickerPreferenceControllerTest {
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<TimeZonePickerPreferenceController>
+            mPreferenceControllerHelper;
+    private TimeZonePickerPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TimeZonePickerPreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void testRefreshUi_disabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, 1);
+        mController.refreshUi();
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_enabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, 0);
+        mController.refreshUi();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void testRefreshUi_fromBroadcastReceiver_disabled() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.STARTED);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, 1);
+        mContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_fromBroadcastReceiver_enabled() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.STARTED);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, 0);
+        mContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datetime/TimeZonePickerScreenPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datetime/TimeZonePickerScreenPreferenceControllerTest.java
new file mode 100644
index 0000000..c07ed5f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datetime/TimeZonePickerScreenPreferenceControllerTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2018 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.car.settings.datetime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.datetime.ZoneGetter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class TimeZonePickerScreenPreferenceControllerTest {
+
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<TimeZonePickerScreenPreferenceController>
+            mPreferenceControllerHelper;
+    private TimeZonePickerScreenPreferenceController mController;
+    @Mock
+    private AlarmManager mAlarmManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
+                TimeZonePickerScreenPreferenceController.class, mPreferenceGroup);
+        mController = mPreferenceControllerHelper.getController();
+
+        // Test setup.
+        mController.mAlarmManager = mAlarmManager;
+    }
+
+    @Test
+    public void testOnCreate_hasElements() {
+        List<Map<String, Object>> testTimeZones = new ArrayList<>();
+        testTimeZones.add(
+                createTimeZoneMap("testKey1", "Midway", "GMT-11:00", -1100));
+        testTimeZones.add(
+                createTimeZoneMap("testKey2", "Tijuana", "GMT-07:00", -700));
+        testTimeZones.add(
+                createTimeZoneMap("testKey3", "Coordinated Universal Time", "GMT+00:00", 0));
+        testTimeZones.add(
+                createTimeZoneMap("testKey4", "Kabul", "GMT+04:30", 430));
+        mController.setZonesList(testTimeZones);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(4);
+    }
+
+    @Test
+    public void testOnPreferenceClick_setTimeZoneCalled() {
+        List<Map<String, Object>> testTimeZone = new ArrayList<>();
+        testTimeZone.add(createTimeZoneMap("testKey", "London", "GMT+01:00", 100));
+        mController.setZonesList(testTimeZone);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        Preference preference = mPreferenceGroup.findPreference("testKey");
+        preference.performClick();
+        verify(mAlarmManager).setTimeZone("testKey");
+    }
+
+    @Test
+    public void testOnPreferenceClick_fragmentControllerGoBack() {
+        List<Map<String, Object>> testTimeZone = new ArrayList<>();
+        testTimeZone.add(createTimeZoneMap("testKey", "London", "GMT+01:00", 100));
+        mController.setZonesList(testTimeZone);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        Preference preference = mPreferenceGroup.findPreference("testKey");
+        preference.performClick();
+        verify(mPreferenceControllerHelper.getMockFragmentController()).goBack();
+    }
+
+    @Test
+    public void testOnPreferenceClick_broadcastFired() {
+        List<Map<String, Object>> testTimeZone = new ArrayList<>();
+        testTimeZone.add(createTimeZoneMap("testKey", "London", "GMT+01:00", 100));
+        mController.setZonesList(testTimeZone);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        Preference preference = mPreferenceGroup.findPreference("testKey");
+        preference.performClick();
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired.size()).isEqualTo(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
+    }
+
+    @Test
+    public void testTimeZonesComparator() {
+        List<Map<String, Object>> testTimeZones = new ArrayList<>();
+        testTimeZones.add(createTimeZoneMap("testKey1", "Oral",
+                "GMT+05:00", 500));
+        testTimeZones.add(createTimeZoneMap("testKey2", "Kathmandu",
+                "GMT+05:45", 545));
+        testTimeZones.add(createTimeZoneMap("testKey3", "Brazzaville",
+                "GMT+01:00", 100));
+        testTimeZones.add(createTimeZoneMap("testKey4", "Casablanca",
+                "GMT+01:00", 100));
+        testTimeZones.add(createTimeZoneMap("testKey5", "Nuuk",
+                "GMT-02:00", -200));
+        testTimeZones.add(createTimeZoneMap("testKey6", "St. John's",
+                "GMT-02:30", -230));
+        mController.setZonesList(testTimeZones);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+
+        List<String> computedOrder = new ArrayList<>();
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            computedOrder.add(mPreferenceGroup.getPreference(i).getTitle().toString());
+        }
+
+        assertThat(computedOrder).containsExactly("St. John's", "Nuuk", "Brazzaville", "Casablanca",
+                "Oral", "Kathmandu");
+    }
+
+    private Map<String, Object> createTimeZoneMap(String key, String timeZone, String offset,
+            int offsetValue) {
+        Map<String, Object> map = new HashMap<>();
+        map.put(ZoneGetter.KEY_ID, key);
+        map.put(ZoneGetter.KEY_DISPLAY_LABEL, timeZone);
+        map.put(ZoneGetter.KEY_OFFSET_LABEL, offset);
+        map.put(ZoneGetter.KEY_OFFSET, offsetValue);
+        return map;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/development/DevelopmentSettingsUtilTest.java b/tests/robotests/src/com/android/car/settings/development/DevelopmentSettingsUtilTest.java
new file mode 100644
index 0000000..55eb9bf
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/development/DevelopmentSettingsUtilTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 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.car.settings.development;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.provider.Settings;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowLocalBroadcastManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowLocalBroadcastManager.class})
+public class DevelopmentSettingsUtilTest {
+
+    private Context mContext;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowLocalBroadcastManager.reset();
+    }
+
+    @Test
+    public void isEnabled_settingsOff_isAdmin_notDemo_shouldReturnFalse() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+
+        assertThat(DevelopmentSettingsUtil.isDevelopmentSettingsEnabled(mContext,
+                mCarUserManagerHelper)).isFalse();
+    }
+
+    @Test
+    public void isEnabled_settingsOn_isAdmin_notDemo_shouldReturnTrue() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+        assertThat(DevelopmentSettingsUtil.isDevelopmentSettingsEnabled(mContext,
+                mCarUserManagerHelper)).isTrue();
+    }
+
+    @Test
+    public void isEnabled_settingsOn_notAdmin_notDemo_shouldReturnFalse() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+
+        assertThat(DevelopmentSettingsUtil.isDevelopmentSettingsEnabled(mContext,
+                mCarUserManagerHelper)).isFalse();
+    }
+
+    @Test
+    public void isEnabled_settingsOn_notAdmin_isDemo_shouldReturnTrue() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(true);
+
+        assertThat(DevelopmentSettingsUtil.isDevelopmentSettingsEnabled(mContext,
+                mCarUserManagerHelper)).isTrue();
+    }
+
+    @Test
+    public void isEnabled_settingsOff_notAdmin_isDemo_shouldReturnFalse() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(true);
+
+        assertThat(DevelopmentSettingsUtil.isDevelopmentSettingsEnabled(mContext,
+                mCarUserManagerHelper)).isFalse();
+    }
+
+    @Test
+    public void setDevelopmentSettingsEnabled_setTrue() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+
+        DevelopmentSettingsUtil.setDevelopmentSettingsEnabled(mContext, true);
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0)).isEqualTo(1);
+    }
+
+    @Test
+    public void setDevelopmentSettingsEnabled_setFalse() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+        DevelopmentSettingsUtil.setDevelopmentSettingsEnabled(mContext, false);
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1)).isEqualTo(0);
+    }
+
+    @Test
+    public void isDeviceProvisioned_true() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                1);
+        assertThat(DevelopmentSettingsUtil.isDeviceProvisioned(mContext)).isTrue();
+    }
+
+    @Test
+    public void isDeviceProvisioned_false() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                0);
+        assertThat(DevelopmentSettingsUtil.isDeviceProvisioned(mContext)).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/display/AdaptiveBrightnessTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/display/AdaptiveBrightnessTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..a965262
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/display/AdaptiveBrightnessTogglePreferenceControllerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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.car.settings.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AdaptiveBrightnessTogglePreferenceControllerTest {
+
+    private Context mContext;
+    private AdaptiveBrightnessTogglePreferenceController mController;
+    private TwoStatePreference mTwoStatePreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mTwoStatePreference = new SwitchPreference(mContext);
+        PreferenceControllerTestHelper<AdaptiveBrightnessTogglePreferenceController>
+                preferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AdaptiveBrightnessTogglePreferenceController.class, mTwoStatePreference);
+        mController = preferenceControllerHelper.getController();
+        preferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void testRefreshUi_manualMode_isNotChecked() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        mController.refreshUi();
+        assertThat(mTwoStatePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_automaticMode_isChecked() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        mController.refreshUi();
+        assertThat(mTwoStatePreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void testHandlePreferenceChanged_setFalse() {
+        mTwoStatePreference.callChangeListener(false);
+        int brightnessMode = Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        assertThat(brightnessMode).isEqualTo(Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+    }
+
+    @Test
+    public void testHandlePreferenceChanged_setTrue() {
+        mTwoStatePreference.callChangeListener(true);
+        int brightnessMode = Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        assertThat(brightnessMode).isEqualTo(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/display/BrightnessLevelPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/display/BrightnessLevelPreferenceControllerTest.java
new file mode 100644
index 0000000..237333d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/display/BrightnessLevelPreferenceControllerTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 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.car.settings.display;
+
+import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX;
+import static com.android.settingslib.display.BrightnessUtils.convertLinearToGamma;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.PowerManager;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.SeekBarPreference;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowPowerManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowPowerManager.class})
+public class BrightnessLevelPreferenceControllerTest {
+
+    private static final int CURRENT_USER = 10;
+    private Context mContext;
+    private BrightnessLevelPreferenceController mController;
+    private SeekBarPreference mSeekBarPreference;
+    private int mMin;
+    private int mMax;
+    private int mMid;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private PowerManager mPowerManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mMin = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMinimum);
+        mMax = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMaximum);
+        mMid = (mMax + mMin) / 2;
+
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(CURRENT_USER);
+        ShadowPowerManager.setInstance(mPowerManager);
+        when(mPowerManager.getMinimumScreenBrightnessSetting()).thenReturn(mMin);
+        when(mPowerManager.getMaximumScreenBrightnessSetting()).thenReturn(mMax);
+
+        mSeekBarPreference = new SeekBarPreference(mContext);
+        PreferenceControllerTestHelper<BrightnessLevelPreferenceController>
+                preferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                BrightnessLevelPreferenceController.class, mSeekBarPreference);
+        mController = preferenceControllerHelper.getController();
+        preferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowPowerManager.reset();
+    }
+
+    @Test
+    public void testRefreshUi_maxSet() {
+        mController.refreshUi();
+        assertThat(mSeekBarPreference.getMax()).isEqualTo(GAMMA_SPACE_MAX);
+    }
+
+    @Test
+    public void testRefreshUi_minValue() {
+        Settings.System.putIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS, mMin,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+
+        mController.refreshUi();
+        assertThat(mSeekBarPreference.getValue()).isEqualTo(0);
+    }
+
+    @Test
+    public void testRefreshUi_maxValue() {
+        Settings.System.putIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS, mMax,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+
+        mController.refreshUi();
+        assertThat(mSeekBarPreference.getValue()).isEqualTo(GAMMA_SPACE_MAX);
+    }
+
+    @Test
+    public void testRefreshUi_midValue() {
+        Settings.System.putIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS, mMid,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+
+        mController.refreshUi();
+        assertThat(mSeekBarPreference.getValue()).isEqualTo(
+                convertLinearToGamma(mMid,
+                        mMin, mMax));
+    }
+
+    @Test
+    public void testHandlePreferenceChanged_minValue() throws Settings.SettingNotFoundException {
+        mSeekBarPreference.callChangeListener(0);
+        int currentSettingsVal = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+        assertThat(currentSettingsVal).isEqualTo(mMin);
+    }
+
+    @Test
+    public void testHandlePreferenceChanged_maxValue() throws Settings.SettingNotFoundException {
+        mSeekBarPreference.callChangeListener(GAMMA_SPACE_MAX);
+        int currentSettingsVal = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+        assertThat(currentSettingsVal).isEqualTo(mMax);
+    }
+
+    @Test
+    public void testHandlePreferenceChanged_midValue() throws Settings.SettingNotFoundException {
+        mSeekBarPreference.callChangeListener(convertLinearToGamma(mMid, mMin, mMax));
+        int currentSettingsVal = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS,
+                mCarUserManagerHelper.getCurrentProcessUserId());
+        assertThat(currentSettingsVal).isEqualTo(mMid);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/inputmethod/EnabledKeyboardPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/inputmethod/EnabledKeyboardPreferenceControllerTest.java
new file mode 100644
index 0000000..8cc7aad
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/inputmethod/EnabledKeyboardPreferenceControllerTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowDevicePolicyManager;
+import com.android.car.settings.testutils.ShadowInputMethodManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowInputMethodManager.class, ShadowDevicePolicyManager.class})
+public class EnabledKeyboardPreferenceControllerTest {
+    private static final String DUMMY_LABEL = "dummy label";
+    private static final String DUMMY_ID = "dummy id";
+    private static final String DUMMY_SETTINGS_ACTIVITY = "dummy settings activity";
+    private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+    private static final String ALLOWED_PACKAGE_NAME = "allowed package name";
+    private static final String DISALLOWED_PACKAGE_NAME = "disallowed package name";
+    private List<String> mPermittedList;
+    private PreferenceControllerTestHelper<EnabledKeyboardPreferenceController> mControllerHelper;
+    private PreferenceGroup mPreferenceGroup;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                EnabledKeyboardPreferenceController.class, mPreferenceGroup);
+
+        mPermittedList = new ArrayList<>();
+        mPermittedList.add(DUMMY_PACKAGE_NAME);
+        mPermittedList.add(ALLOWED_PACKAGE_NAME);
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void refreshUi_permitAllInputMethods() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(DUMMY_PACKAGE_NAME);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void refreshUi_hasAllowedImeByOrganization() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(
+                mPermittedList);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void refreshUi_disallowedByOrganization() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(
+                mPermittedList);
+        List<InputMethodInfo> infos = createInputMethodInfoList(DISALLOWED_PACKAGE_NAME);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_verifyPreferenceIcon() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        Preference preference = mPreferenceGroup.getPreference(0);
+        assertThat(preference.getIcon()).isEqualTo(
+                InputMethodUtil.getPackageIcon(mContext.getPackageManager(), infos.get(0)));
+    }
+
+    @Test
+    public void refreshUi_verifyPreferenceTitle() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        Preference preference = mPreferenceGroup.getPreference(0);
+        assertThat(preference.getTitle()).isEqualTo(
+                InputMethodUtil.getPackageLabel(mContext.getPackageManager(), infos.get(0)));
+    }
+
+    @Test
+    public void refreshUi_verifyPreferenceSummary() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        InputMethodManager inputMethodManager =
+                (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+        Preference preference = mPreferenceGroup.getPreference(0);
+        assertThat(preference.getSummary()).isEqualTo(
+                InputMethodUtil.getSummaryString(mContext, inputMethodManager, infos.get(0)));
+    }
+
+    @Test
+    public void performClick_launchSettingsActivity() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(DISALLOWED_PACKAGE_NAME);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        Preference preference = mPreferenceGroup.getPreference(0);
+        preference.performClick();
+
+        Intent intent = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(intent.getComponent().getClassName()).isEqualTo(DUMMY_SETTINGS_ACTIVITY);
+        assertThat(intent.getComponent().getPackageName()).isEqualTo(DISALLOWED_PACKAGE_NAME);
+    }
+
+    @Test
+    public void performClick_noSettingsActivity_noCrash() {
+        // Set to true if you'd like Robolectric to strictly simulate the real Android behavior when
+        // calling {@link Context#startActivity(android.content.Intent)}. Real Android throws a
+        // {@link android.content.ActivityNotFoundException} if given an {@link Intent} that is not
+        // known to the {@link android.content.pm.PackageManager.
+        // DUMMY_SETTINGS_ACTIVITY shouldn't exist, so it throws ActivityNotFoundException.
+        ShadowApplication.getInstance().checkActivities(true);
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(DISALLOWED_PACKAGE_NAME);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        Preference preference = mPreferenceGroup.getPreference(0);
+        preference.performClick();
+
+        Intent intent = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(intent).isNull();
+    }
+
+    private List<InputMethodInfo> createInputMethodInfoList(String packageName) {
+        List<InputMethodInfo> infos = new ArrayList<>();
+        PackageManager packageManager = mContext.getPackageManager();
+        infos.add(createMockInputMethodInfoWithSubtypes(
+                packageManager, getShadowInputMethodManager(mContext), packageName));
+        return infos;
+    }
+
+    private static InputMethodInfo createMockInputMethodInfoWithSubtypes(
+            PackageManager packageManager, ShadowInputMethodManager inputMethodManager,
+            String packageName) {
+        InputMethodInfo mockInfo = createMockInputMethodInfo(packageManager, packageName);
+        List<InputMethodSubtype> subtypes = createSubtypes();
+        inputMethodManager.setEnabledInputMethodSubtypeList(subtypes);
+
+        return mockInfo;
+    }
+
+    private static InputMethodInfo createMockInputMethodInfo(
+            PackageManager packageManager, String packageName) {
+        InputMethodInfo mockInfo = mock(InputMethodInfo.class);
+        when(mockInfo.getPackageName()).thenReturn(packageName);
+        when(mockInfo.getId()).thenReturn(DUMMY_ID);
+        when(mockInfo.loadLabel(packageManager)).thenReturn(DUMMY_LABEL);
+        when(mockInfo.getServiceInfo()).thenReturn(new ServiceInfo());
+        when(mockInfo.getSettingsActivity()).thenReturn(DUMMY_SETTINGS_ACTIVITY);
+        return mockInfo;
+    }
+
+    private static List<InputMethodSubtype> createSubtypes() {
+        List<InputMethodSubtype> subtypes = new ArrayList<>();
+        subtypes.add(createSubtype(1, "en_US"));
+        subtypes.add(createSubtype(2, "de_BE"));
+        subtypes.add(createSubtype(3, "oc-FR"));
+        return subtypes;
+    }
+
+    private static InputMethodSubtype createSubtype(int id, String locale) {
+        return new InputMethodSubtypeBuilder().setSubtypeId(id).setSubtypeLocale(locale)
+                .setIsAuxiliary(false).setIsAsciiCapable(true).build();
+    }
+
+    private static ShadowInputMethodManager getShadowInputMethodManager(Context context) {
+        return Shadow.extract(context.getSystemService(Context.INPUT_METHOD_SERVICE));
+    }
+
+    private static ShadowDevicePolicyManager getShadowDevicePolicyManager(Context context) {
+        return Shadow.extract(context.getSystemService(Context.DEVICE_POLICY_SERVICE));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/inputmethod/InputMethodUtilTest.java b/tests/robotests/src/com/android/car/settings/inputmethod/InputMethodUtilTest.java
new file mode 100644
index 0000000..2067089
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/inputmethod/InputMethodUtilTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.provider.Settings;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class InputMethodUtilTest {
+    private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+    private static final String DUMMY_LABEL = "dummy label";
+    private static final String DUMMY_SETTINGS_ACTIVITY = "dummy settings activity";
+    private static final String SUBTYPES_STRING =
+            "English (United States), German (Belgium), and Occitan (France)";
+    private static final String DUMMY_ENABLED_INPUT_METHODS =
+            "com.google.android.googlequicksearchbox/com.google.android.voicesearch.ime"
+                    + ".VoiceInputMethodService:com.google.android.apps.automotive.inputmethod/"
+                    + ".InputMethodService";
+    private static final String DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT =
+            "com.google.android.apps.automotive.inputmethod/.InputMethodService";
+    private static final String DUMMY_ENABLED_INPUT_METHOD_ID =
+            "com.google.android.googlequicksearchbox/com.google.android.voicesearch.ime"
+                    + ".VoiceInputMethodService";
+    private static final String DUMMY_DISABLED_INPUT_METHOD_ID = "disabled input method id";
+    private Context mContext;
+    private List<InputMethodInfo> mDummyEnabledInputMethodsListAllDefaultable;
+    private List<InputMethodInfo> mDummyEnabledInputMethodsListOneDefaultable;
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private InputMethodManager mInputMethodManager;
+    @Mock
+    private Drawable mIcon;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS, DUMMY_ENABLED_INPUT_METHODS);
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD, DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+
+        mDummyEnabledInputMethodsListOneDefaultable = Arrays
+                .stream(DUMMY_ENABLED_INPUT_METHODS.split(String.valueOf(InputMethodUtil
+                        .INPUT_METHOD_DELIMITER))).collect(Collectors.toList()).stream().map(
+                            result -> {
+                                InputMethodInfo info = createMockInputMethodInfo(
+                                        mPackageManager, DUMMY_PACKAGE_NAME);
+                                when(info.getId()).thenReturn(result);
+                                when(info.isDefault(mContext)).thenReturn(result.equals(
+                                        DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT));
+                                return info;
+                            }).collect(Collectors.toList());
+        mDummyEnabledInputMethodsListAllDefaultable = Arrays
+                .stream(DUMMY_ENABLED_INPUT_METHODS.split(String.valueOf(InputMethodUtil
+                        .INPUT_METHOD_DELIMITER))).collect(Collectors.toList()).stream().map(
+                                result -> {
+                                    InputMethodInfo info = createMockInputMethodInfo(
+                                            mPackageManager, DUMMY_PACKAGE_NAME);
+                                    when(info.getId()).thenReturn(result);
+                                    when(info.isDefault(mContext)).thenReturn(true);
+                                    return info;
+                                }).collect(Collectors.toList());
+    }
+
+    @Test
+    public void getPackageIcon_hasApplicationIcon() throws NameNotFoundException {
+        InputMethodInfo info = createMockInputMethodInfoWithSubtypes(mPackageManager,
+                mInputMethodManager, DUMMY_PACKAGE_NAME);
+        when(mPackageManager.getApplicationIcon(eq(info.getPackageName()))).thenReturn(mIcon);
+        assertThat(InputMethodUtil.getPackageIcon(mPackageManager, info)).isEqualTo(mIcon);
+    }
+
+    @Test
+    public void getPackageIcon_noApplicationIcon() throws NameNotFoundException {
+        InputMethodInfo info = createMockInputMethodInfoWithSubtypes(mPackageManager,
+                mInputMethodManager, DUMMY_PACKAGE_NAME);
+        when(mPackageManager.getApplicationIcon(DUMMY_PACKAGE_NAME)).thenThrow(
+                new NameNotFoundException());
+        assertThat(InputMethodUtil.getPackageIcon(mPackageManager, info)).isEqualTo(
+                InputMethodUtil.NO_ICON);
+    }
+
+    @Test
+    public void getPackageLabel() {
+        InputMethodInfo info = createMockInputMethodInfoWithSubtypes(mPackageManager,
+                mInputMethodManager, DUMMY_PACKAGE_NAME);
+        assertThat(InputMethodUtil.getPackageLabel(mPackageManager, info)).isEqualTo(
+                DUMMY_LABEL);
+    }
+
+    @Test
+    public void getSummaryString() {
+        InputMethodInfo info = createMockInputMethodInfoWithSubtypes(mPackageManager,
+                mInputMethodManager, DUMMY_PACKAGE_NAME);
+        assertThat(InputMethodUtil.getSummaryString(mContext, mInputMethodManager, info)).isEqualTo(
+                SUBTYPES_STRING);
+    }
+
+    @Test
+    public void isInputMethodEnabled_isDisabled_returnsFalse() {
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_DISABLED_INPUT_METHOD_ID);
+
+        assertThat(InputMethodUtil.isInputMethodEnabled(mContext.getContentResolver(), info))
+                .isFalse();
+    }
+
+    @Test
+    public void isInputMethodEnabled_isEnabled_returnsTrue() {
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+
+        assertThat(InputMethodUtil.isInputMethodEnabled(mContext.getContentResolver(), info))
+                .isTrue();
+    }
+
+    @Test
+    public void enableInputMethod_alreadyEnabled_remainsUnchanged() {
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+
+        InputMethodUtil.enableInputMethod(mContext.getContentResolver(), info);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS)).isEqualTo(DUMMY_ENABLED_INPUT_METHODS);
+    }
+
+    @Test
+    public void enableInputMethod_noEnabledInputMethods_addsIME() {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS, "");
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+
+        InputMethodUtil.enableInputMethod(mContext.getContentResolver(), info);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS)).isEqualTo(
+                DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+    }
+
+    @Test
+    public void enableInputMethod_someEnabledInputMethods_addsIME() {
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_DISABLED_INPUT_METHOD_ID);
+
+        InputMethodUtil.enableInputMethod(mContext.getContentResolver(), info);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS)).isEqualTo(
+                DUMMY_ENABLED_INPUT_METHODS + ":"
+                        + DUMMY_DISABLED_INPUT_METHOD_ID);
+    }
+
+    @Test
+    public void disableInputMethod_notEnabled_remainsUnchanged() {
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_DISABLED_INPUT_METHOD_ID);
+        when(mInputMethodManager.getEnabledInputMethodList())
+                .thenReturn(mDummyEnabledInputMethodsListAllDefaultable);
+
+        InputMethodUtil.disableInputMethod(mContext, mInputMethodManager, info);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS)).isEqualTo(DUMMY_ENABLED_INPUT_METHODS);
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD)).isEqualTo(
+                DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+    }
+
+    @Test
+    public void disableInputMethod_notDefault_removesIMEWhileDefaultRemainsSame() {
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_ENABLED_INPUT_METHOD_ID);
+        when(mInputMethodManager.getEnabledInputMethodList())
+                .thenReturn(mDummyEnabledInputMethodsListAllDefaultable);
+
+        InputMethodUtil.disableInputMethod(mContext, mInputMethodManager, info);
+
+        assertThat(splitConcatenatedIdsIntoSet(Settings.Secure.getString(mContext
+                .getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS))).isEqualTo(
+                splitConcatenatedIdsIntoSet(DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT));
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD)).isEqualTo(
+                DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+    }
+
+    @Test
+    public void disableInputMethod_twoDefaultableIMEsEnabled_removesIMEAndChangesDefault() {
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+        when(mInputMethodManager.getEnabledInputMethodList())
+                .thenReturn(mDummyEnabledInputMethodsListAllDefaultable);
+
+        InputMethodUtil.disableInputMethod(mContext, mInputMethodManager, info);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS)).isEqualTo(
+                DUMMY_ENABLED_INPUT_METHOD_ID);
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD)).isEqualTo(
+                DUMMY_ENABLED_INPUT_METHOD_ID);
+    }
+
+    @Test
+    public void disableInputMethod_isDefaultWithNoOtherDefaultableEnabled_remainsUnchanged() {
+        InputMethodInfo info = createMockInputMethodInfo(mPackageManager, DUMMY_PACKAGE_NAME);
+        when(info.getId()).thenReturn(DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+        when(mInputMethodManager.getEnabledInputMethodList())
+                .thenReturn(mDummyEnabledInputMethodsListOneDefaultable);
+
+        InputMethodUtil.disableInputMethod(mContext, mInputMethodManager, info);
+
+        assertThat(splitConcatenatedIdsIntoSet(Settings.Secure.getString(mContext
+                .getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS))).isEqualTo(
+                splitConcatenatedIdsIntoSet(DUMMY_ENABLED_INPUT_METHODS));
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD)).isEqualTo(
+                DUMMY_ENABLED_INPUT_METHOD_ID_DEFAULT);
+    }
+
+    private static InputMethodInfo createMockInputMethodInfoWithSubtypes(
+            PackageManager packageManager, InputMethodManager inputMethodManager,
+            String packageName) {
+        InputMethodInfo mockInfo = createMockInputMethodInfo(packageManager, packageName);
+        List<InputMethodSubtype> subtypes = createSubtypes();
+        when(inputMethodManager.getEnabledInputMethodSubtypeList(
+                eq(mockInfo), anyBoolean())).thenReturn(subtypes);
+        return mockInfo;
+    }
+
+    private static InputMethodInfo createMockInputMethodInfo(
+            PackageManager packageManager, String packageName) {
+        InputMethodInfo mockInfo = mock(InputMethodInfo.class);
+        when(mockInfo.getPackageName()).thenReturn(packageName);
+        when(mockInfo.loadLabel(packageManager)).thenReturn(DUMMY_LABEL);
+        when(mockInfo.getServiceInfo()).thenReturn(new ServiceInfo());
+        when(mockInfo.getSettingsActivity()).thenReturn(DUMMY_SETTINGS_ACTIVITY);
+        return mockInfo;
+    }
+
+    private static List<InputMethodSubtype> createSubtypes() {
+        List<InputMethodSubtype> subtypes = new ArrayList<>();
+        subtypes.add(createSubtype(1, "en_US"));
+        subtypes.add(createSubtype(2, "de_BE"));
+        subtypes.add(createSubtype(3, "oc-FR"));
+        return subtypes;
+    }
+
+    private static InputMethodSubtype createSubtype(int id, String locale) {
+        return new InputMethodSubtypeBuilder().setSubtypeId(id).setSubtypeLocale(locale)
+                .setIsAuxiliary(false).setIsAsciiCapable(true).build();
+    }
+
+    private Set<String> splitConcatenatedIdsIntoSet(String ids) {
+        Set<String> result = new HashSet<>();
+
+        if (ids == null || ids.isEmpty()) {
+            return result;
+        }
+
+        InputMethodUtil.sInputMethodSplitter.setString(ids);
+        while (InputMethodUtil.sInputMethodSplitter.hasNext()) {
+            result.add(InputMethodUtil.sInputMethodSplitter.next());
+        }
+
+        return result;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/inputmethod/KeyboardManagementPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/inputmethod/KeyboardManagementPreferenceControllerTest.java
new file mode 100644
index 0000000..0bc50ef
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/inputmethod/KeyboardManagementPreferenceControllerTest.java
@@ -0,0 +1,1066 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowDevicePolicyManager;
+import com.android.car.settings.testutils.ShadowInputMethodManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowInputMethodManager.class, ShadowDevicePolicyManager.class})
+public class KeyboardManagementPreferenceControllerTest {
+    private static final String DUMMY_LABEL = "dummy label";
+    private static final String DUMMY_SETTINGS_ACTIVITY = "dummy settings activity";
+    private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+    private static final String DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE =
+            "dummy id defaultable direct boot aware";
+    private static final String DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE =
+            "dummy id defaultable not direct boot aware";
+    private static final String DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE =
+            "dummy id not defaultable direct boot aware";
+    private static final String DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE =
+            "dummy id not defaultable not direct boot aware";
+    private static final String DISALLOWED_PACKAGE_NAME = "disallowed package name";
+    private static final String ALLOWED_PACKAGE_NAME = "allowed package name";
+    private List<String> mPermittedList;
+    private PreferenceControllerTestHelper<KeyboardManagementPreferenceController>
+            mControllerHelper;
+    private PreferenceGroup mPreferenceGroup;
+    private Context mContext;
+    private InputMethodManager mInputMethodManager;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                KeyboardManagementPreferenceController.class, mPreferenceGroup);
+        mInputMethodManager = (InputMethodManager) mContext.getSystemService(Context
+                .INPUT_METHOD_SERVICE);
+
+        mPermittedList = new ArrayList<>();
+        mPermittedList.add(DUMMY_PACKAGE_NAME);
+        mPermittedList.add(ALLOWED_PACKAGE_NAME);
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void refreshUi_permitAllInputMethods_preferenceCountIs4() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+        getShadowInputMethodManager(mContext).setInputMethodList(infos);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(4);
+    }
+
+    @Test
+    public void refreshUi_multiplteAllowedImeByOrganization_allPreferencesVisible() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(
+                mPermittedList);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+        getShadowInputMethodManager(mContext).setInputMethodList(infos);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            assertThat(mPreferenceGroup.getPreference(i).isVisible()).isTrue();
+        }
+    }
+
+    @Test
+    public void refreshUi_multipleEnabledInputMethods_allPreferencesEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(
+                mPermittedList);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+        getShadowInputMethodManager(mContext).setInputMethodList(infos);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            assertThat(mPreferenceGroup.getPreference(i).isEnabled()).isTrue();
+        }
+    }
+
+    @Test
+    public void refreshUi_multipleEnabledInputMethods_allPreferencesChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(
+                mPermittedList);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+        getShadowInputMethodManager(mContext).setInputMethodList(infos);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            assertThat(((SwitchPreference) mPreferenceGroup.getPreference(i)).isChecked())
+                    .isTrue();
+        }
+    }
+
+    @Test
+    public void refreshUi_disallowedByOrganization_noPreferencesShown() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(
+                mPermittedList);
+        List<InputMethodInfo> infos = createInputMethodInfoList(DISALLOWED_PACKAGE_NAME,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+        getShadowInputMethodManager(mContext).setInputMethodList(infos);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void refreshUi_verifyPreferenceIcon() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+        getShadowInputMethodManager(mContext).setInputMethodList(infos);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        Preference preference = mPreferenceGroup.getPreference(0);
+        assertThat(preference.getIcon()).isEqualTo(
+                InputMethodUtil.getPackageIcon(mContext.getPackageManager(), infos.get(0)));
+    }
+
+    @Test
+    public void refreshUi_verifyPreferenceTitle() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+        getShadowInputMethodManager(mContext).setInputMethodList(infos);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        Preference preference = mPreferenceGroup.getPreference(0);
+        assertThat(preference.getTitle()).isEqualTo(
+                InputMethodUtil.getPackageLabel(mContext.getPackageManager(), infos.get(0)));
+    }
+
+    @Test
+    public void refreshUi_verifyPreferenceSummary() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        List<InputMethodInfo> infos = createInputMethodInfoList(ALLOWED_PACKAGE_NAME,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+        getShadowInputMethodManager(mContext).setInputMethodList(infos);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+
+        InputMethodManager inputMethodManager =
+                (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+        Preference preference = mPreferenceGroup.getPreference(0);
+        assertThat(preference.getSummary()).isEqualTo(
+                InputMethodUtil.getSummaryString(mContext, inputMethodManager, infos.get(0)));
+    }
+
+    @Test
+    public void refreshUi_oneInputMethod_noneEnabled_oneInputMethodPreferenceInView() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void refreshUi_oneInputMethod_noneEnabled_preferenceEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreferenceGroup.getPreference(0).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_oneInputMethod_noneEnabled_preferenceNotChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(((SwitchPreference) mPreferenceGroup.getPreference(0)).isChecked())
+                .isFalse();
+    }
+
+    @Test
+    public void performClick_toggleTrue_securityDialogShown() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmationDialogFragment.class),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+    }
+
+    @Test
+    public void performClick_toggleTrue_showSecurityDialog_positive_noOtherPreferenceAdded() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getKey()).isEqualTo(
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE);
+    }
+
+    @Test
+    public void performClick_toggleTrue_showSecurityDialog_positive_preferenceChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(((SwitchPreference) mPreferenceGroup.getPreference(0)).isChecked())
+                .isTrue();
+    }
+
+    @Test
+    public void performClick_toggleTrue_showSecurityDialog_positive_preferenceEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(mPreferenceGroup.getPreference(0).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void performClick_toggleTrue_showSecurityDialog_positive_inputMethodEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(mInputMethodManager.getEnabledInputMethodList().get(0).getId())
+                .isEqualTo(DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE);
+    }
+
+    @Test
+    public void performClick_toggleTrue_showSecurityDialog_negative_noOtherPreferenceAdded() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getKey()).isEqualTo(
+                DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE);
+    }
+
+    @Test
+    public void performClick_toggleTrue_showSecurityDialog_negative_preferenceEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+        assertThat(mPreferenceGroup.getPreference(0).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void performClick_toggleTrue_showSecurityDialog_negative_inputMethodDisabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+        assertThat(((SwitchPreference) mPreferenceGroup.getPreference(0)).isChecked())
+                .isFalse();
+    }
+
+    @Test
+    public void performClick_toggleTrue_directBootWarningShown() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmationDialogFragment.class),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+    }
+
+    @Test
+    public void performClick_toggleTrue_showDirectBootDialog_positive_noOtherPreferenceAdded() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        ArgumentCaptor<ConfirmationDialogFragment> bootDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(bootDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+        dialogFragment = bootDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getKey()).isEqualTo(
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+    }
+
+    @Test
+    public void performClick_toggleTrue_showDirectBootDialog_positive_preferenceChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        ArgumentCaptor<ConfirmationDialogFragment> bootDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(bootDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+        dialogFragment = bootDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(((SwitchPreference) mPreferenceGroup.getPreference(0)).isChecked())
+                .isTrue();
+    }
+
+    @Test
+    public void performClick_toggleTrue_showDirectBootDialog_positive_preferenceEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        ArgumentCaptor<ConfirmationDialogFragment> bootDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(bootDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+        dialogFragment = bootDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(mPreferenceGroup.getPreference(0).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void performClick_toggleTrue_showDirectBootDialog_positive_inputMethodEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        ArgumentCaptor<ConfirmationDialogFragment> bootDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(bootDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+        dialogFragment = bootDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(mInputMethodManager.getEnabledInputMethodList().get(0).getId())
+                .isEqualTo(DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+    }
+
+    @Test
+    public void performClick_toggleTrue_showDirectBootDialog_negative_noOtherPreferenceAdded() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        ArgumentCaptor<ConfirmationDialogFragment> bootDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(bootDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+        dialogFragment = bootDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getKey()).isEqualTo(
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+    }
+
+    @Test
+    public void performClick_toggleTrue_showDirectBootDialog_negative_preferenceNotChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        ArgumentCaptor<ConfirmationDialogFragment> bootDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(bootDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+        dialogFragment = bootDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+
+        assertThat(((SwitchPreference) mPreferenceGroup.getPreference(0)).isChecked())
+                .isFalse();
+    }
+
+    @Test
+    public void performClick_toggleTrue_showDirectBootDialog_negative_preferenceEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        ArgumentCaptor<ConfirmationDialogFragment> bootDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(bootDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+        dialogFragment = bootDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+        assertThat(mPreferenceGroup.getPreference(0).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void performClick_toggleTrue_showDirectBootDialog_negative_inputMethodDisabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(new ArrayList<>());
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> securityDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                securityDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = securityDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        ArgumentCaptor<ConfirmationDialogFragment> bootDialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(bootDialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.DIRECT_BOOT_WARN_DIALOG_TAG));
+        dialogFragment = bootDialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+        assertThat(mInputMethodManager.getEnabledInputMethodList().size())
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void performClick_toggleFalse_noOtherPreferenceAdded() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getKey()).isEqualTo(
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE);
+    }
+
+    @Test
+    public void performClick_toggleFalse_preferenceNotChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        assertThat(((SwitchPreference) mPreferenceGroup.getPreference(0)).isChecked())
+                .isFalse();
+    }
+
+    @Test
+    public void performClick_toggleFalse_preferenceEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        assertThat((mPreferenceGroup.getPreference(0)).isEnabled())
+                .isTrue();
+    }
+
+    @Test
+    public void performClick_toggleFalse_inputMethodDisabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+
+        mControllerHelper.getController().refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+
+        assertThat(mInputMethodManager.getEnabledInputMethodList().size())
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void performClick_toggleFalse_twoDefaultable_notClickDefaultablePreferenceDisabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+
+        mControllerHelper.getController().refreshUi();
+
+        getPreferenceFromGroupByKey(mPreferenceGroup, DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE)
+                .performClick();
+
+        assertThat(getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void performClick_toggleFalse_twoDefaultable_clickedDefaultablePreferenceEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+
+        mControllerHelper.getController().refreshUi();
+
+        getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE).performClick();
+
+        assertThat(getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void performClick_toggleFalse_twoDefaultable_nonDefaultablePreferenceEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+
+        mControllerHelper.getController().refreshUi();
+
+        getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE).performClick();
+
+        assertThat(getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void performClick_toggleFalse_twoDefaultable_clickedDefaultablePreferenceNotChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+
+        mControllerHelper.getController().refreshUi();
+
+        getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE).performClick();
+
+        assertThat(((SwitchPreference) getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE)).isChecked()).isFalse();
+    }
+
+    @Test
+    public void performClick_toggleFalse_twoDefaultable_notClickedDefaultablePreferenceChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+
+        mControllerHelper.getController().refreshUi();
+
+        getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE).performClick();
+
+        assertThat(((SwitchPreference) getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)).isChecked()).isTrue();
+    }
+
+    @Test
+    public void performClick_toggleFalse_twoDefaultable_nonDefaultablePreferenceChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+
+        mControllerHelper.getController().refreshUi();
+
+        getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE).performClick();
+
+        assertThat(((SwitchPreference) getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)).isChecked()).isTrue();
+    }
+
+    @Test
+    public void performClick_toggleTrue_twoDefaultable_allPreferencesEnabled() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+
+        mControllerHelper.getController().refreshUi();
+
+        getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            assertThat(mPreferenceGroup.getPreference(i).isEnabled()).isTrue();
+        }
+    }
+
+    @Test
+    public void performClick_toggleTrue_twoDefaultable_allPreferencesChecked() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE, DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE)
+        );
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(createInputMethodInfoList(
+                ALLOWED_PACKAGE_NAME, DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE,
+                DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE));
+
+        mControllerHelper.getController().refreshUi();
+
+        getPreferenceFromGroupByKey(mPreferenceGroup,
+                DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE).performClick();
+
+        ArgumentCaptor<ConfirmationDialogFragment> dialogCaptor = ArgumentCaptor.forClass(
+                ConfirmationDialogFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).showDialog(dialogCaptor.capture(),
+                eq(KeyboardManagementPreferenceController.SECURITY_WARN_DIALOG_TAG));
+        ConfirmationDialogFragment dialogFragment = dialogCaptor.getValue();
+
+        dialogFragment.onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            assertThat(((SwitchPreference) mPreferenceGroup.getPreference(i)).isChecked())
+                    .isTrue();
+        }
+    }
+
+    private static InputMethodInfo createMockInputMethodInfo(
+            Context context, PackageManager packageManager,
+            ShadowInputMethodManager inputMethodManager, String packageName, String id,
+            boolean isDefaultable, boolean directBootAware) {
+        ServiceInfo mockServiceInfo = mock(ServiceInfo.class);
+        mockServiceInfo.directBootAware = directBootAware;
+
+        InputMethodInfo mockInfo = mock(InputMethodInfo.class);
+        when(mockInfo.getPackageName()).thenReturn(packageName);
+        when(mockInfo.loadLabel(packageManager)).thenReturn(DUMMY_LABEL);
+        when(mockInfo.getServiceInfo()).thenReturn(mockServiceInfo);
+        when(mockInfo.getSettingsActivity()).thenReturn(DUMMY_SETTINGS_ACTIVITY);
+        when(mockInfo.getId()).thenReturn(id);
+        when(mockInfo.isDefault(context)).thenReturn(isDefaultable);
+        List<InputMethodSubtype> subtypes = createSubtypes();
+        inputMethodManager.setEnabledInputMethodSubtypeList(subtypes);
+        return mockInfo;
+    }
+
+    private static Preference getPreferenceFromGroupByKey(PreferenceGroup prefGroup, String key) {
+        for (int i = 0; i < prefGroup.getPreferenceCount(); i++) {
+            Preference pref = prefGroup.getPreference(i);
+            if (pref.getKey().equals(key)) {
+                return pref;
+            }
+        }
+        return null;
+    }
+
+    private static List<InputMethodSubtype> createSubtypes() {
+        List<InputMethodSubtype> subtypes = new ArrayList<>();
+        subtypes.add(createSubtype(1, "en_US"));
+        subtypes.add(createSubtype(2, "de_BE"));
+        subtypes.add(createSubtype(3, "oc-FR"));
+        return subtypes;
+    }
+
+    private static InputMethodSubtype createSubtype(int id, String locale) {
+        return new InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(id)
+                .setSubtypeLocale(locale).setIsAuxiliary(false).setIsAsciiCapable(true).build();
+    }
+
+    private static ShadowInputMethodManager getShadowInputMethodManager(Context context) {
+        return Shadow.extract(context.getSystemService(Context.INPUT_METHOD_SERVICE));
+    }
+
+    private static ShadowDevicePolicyManager getShadowDevicePolicyManager(Context context) {
+        return Shadow.extract(context.getSystemService(Context.DEVICE_POLICY_SERVICE));
+    }
+
+    private List<InputMethodInfo> createInputMethodInfoList(String packageName, String... ids) {
+        List<InputMethodInfo> infos = new ArrayList<>();
+        PackageManager packageManager = mContext.getPackageManager();
+        List<String> idsList = Arrays.asList(ids);
+        idsList.forEach(id -> {
+            boolean defaultable;
+            boolean directBootAware;
+            switch (id) {
+                case DUMMY_ID_DEFAULTABLE_DIRECT_BOOT_AWARE:
+                    defaultable = true;
+                    directBootAware = true;
+                    break;
+                case DUMMY_ID_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE:
+                    defaultable = true;
+                    directBootAware = false;
+                    break;
+                case DUMMY_ID_NOT_DEFAULTABLE_DIRECT_BOOT_AWARE:
+                    defaultable = false;
+                    directBootAware = true;
+                    break;
+                default: //case DUMMY_ID_NOT_DEFAULTABLE_NOT_DIRECT_BOOT_AWARE:
+                    defaultable = false;
+                    directBootAware = false;
+                    break;
+            }
+            infos.add(createMockInputMethodInfo(mContext, packageManager,
+                    getShadowInputMethodManager(mContext), packageName, id, defaultable,
+                    directBootAware));
+        });
+        return infos;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/inputmethod/KeyboardPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/inputmethod/KeyboardPreferenceControllerTest.java
new file mode 100644
index 0000000..52a39ff
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/inputmethod/KeyboardPreferenceControllerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowDevicePolicyManager;
+import com.android.car.settings.testutils.ShadowInputMethodManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowInputMethodManager.class, ShadowDevicePolicyManager.class})
+public class KeyboardPreferenceControllerTest {
+    private static final String EMPTY = "";
+    private static final String DUMMY_LABEL = "dummy label";
+    private static final String DUMMY_LABEL_1 = "dummy label 1";
+    private static final String DUMMY_LABEL_2 = "dummy label 2";
+    private static final String DUMMY_SETTINGS_ACTIVITY = "dummy settings activity";
+    private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+    private static final String ALLOWED_PACKAGE_NAME = "allowed package name";
+    private static final String DISALLOWED_PACKAGE_NAME = "disallowed package name";
+    private List<String> mPermittedList;
+    private PreferenceControllerTestHelper<KeyboardPreferenceController> mControllerHelper;
+    private Preference mPreference;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                KeyboardPreferenceController.class, mPreference);
+
+        mPermittedList = new ArrayList<>();
+        mPermittedList.add(DUMMY_PACKAGE_NAME);
+        mPermittedList.add(ALLOWED_PACKAGE_NAME);
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void refreshUi_noInputMethodInfo() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(Collections.emptyList());
+
+        mControllerHelper.getController().refreshUi();
+        assertThat(mPreference.getSummary()).isEqualTo(EMPTY);
+    }
+
+    @Test
+    public void refreshUi_permitAllInputMethods_hasOneInputMethodInfo() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+
+        List<InputMethodInfo> infos = new ArrayList<>();
+        PackageManager packageManager = mContext.getPackageManager();
+        infos.add(createInputMethodInfo(packageManager, DUMMY_PACKAGE_NAME, DUMMY_LABEL));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+        assertThat(mPreference.getSummary()).isNotNull();
+        assertThat(mPreference.getSummary().toString().contains(DUMMY_LABEL)).isTrue();
+    }
+
+    @Test
+    public void refreshUi_permitAllInputMethods_hasTwoInputMethodInfo() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+
+        List<InputMethodInfo> infos = new ArrayList<>();
+        PackageManager packageManager = mContext.getPackageManager();
+        infos.add(createInputMethodInfo(packageManager, DUMMY_PACKAGE_NAME, DUMMY_LABEL));
+        infos.add(createInputMethodInfo(packageManager, DUMMY_PACKAGE_NAME, DUMMY_LABEL_1));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+        assertThat(mPreference.getSummary()).isNotNull();
+        assertThat(mPreference.getSummary().toString().contains(DUMMY_LABEL)).isTrue();
+        assertThat(mPreference.getSummary().toString().contains(DUMMY_LABEL_1)).isTrue();
+    }
+
+    @Test
+    public void refreshUi_permitAllInputMethods_hasThreeInputMethodInfo() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(null);
+
+        List<InputMethodInfo> infos = new ArrayList<>();
+        PackageManager packageManager = mContext.getPackageManager();
+        infos.add(createInputMethodInfo(packageManager, DUMMY_PACKAGE_NAME, DUMMY_LABEL));
+        infos.add(createInputMethodInfo(packageManager, DUMMY_PACKAGE_NAME, DUMMY_LABEL_1));
+        infos.add(createInputMethodInfo(packageManager, DUMMY_PACKAGE_NAME, DUMMY_LABEL_2));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+        assertThat(mPreference.getSummary()).isNotNull();
+        assertThat(mPreference.getSummary().toString().contains(DUMMY_LABEL)).isTrue();
+        assertThat(mPreference.getSummary().toString().contains(DUMMY_LABEL_1)).isTrue();
+        assertThat(mPreference.getSummary().toString().contains(DUMMY_LABEL_2)).isTrue();
+    }
+
+    @Test
+    public void refreshUi_hasAllowedImeByOrganization() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(
+                mPermittedList);
+
+        List<InputMethodInfo> infos = new ArrayList<>();
+        PackageManager packageManager = mContext.getPackageManager();
+        infos.add(createInputMethodInfo(packageManager, ALLOWED_PACKAGE_NAME, DUMMY_LABEL));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+        assertThat(mPreference.getSummary()).isEqualTo(DUMMY_LABEL);
+    }
+
+    @Test
+    public void refreshUi_disallowedByOrganization() {
+        getShadowDevicePolicyManager(mContext).setPermittedInputMethodsForCurrentUser(
+                mPermittedList);
+
+        List<InputMethodInfo> infos = new ArrayList<>();
+        PackageManager packageManager = mContext.getPackageManager();
+        infos.add(createInputMethodInfo(packageManager, DISALLOWED_PACKAGE_NAME, DUMMY_LABEL));
+        getShadowInputMethodManager(mContext).setEnabledInputMethodList(infos);
+
+        mControllerHelper.getController().refreshUi();
+        assertThat(mPreference.getSummary()).isEqualTo(EMPTY);
+    }
+
+    private static InputMethodInfo createInputMethodInfo(
+            PackageManager packageManager, String packageName, String label) {
+        ResolveInfo resolveInfo = mock(ResolveInfo.class);
+        ServiceInfo serviceInfo = new ServiceInfo();
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = packageName;
+        applicationInfo.enabled = true;
+        applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        serviceInfo.applicationInfo = applicationInfo;
+        serviceInfo.enabled = true;
+        serviceInfo.packageName = packageName;
+        serviceInfo.name = label;
+        serviceInfo.exported = true;
+        serviceInfo.nonLocalizedLabel = label;
+        resolveInfo.serviceInfo = serviceInfo;
+        resolveInfo.nonLocalizedLabel = label;
+        when(resolveInfo.loadLabel(packageManager)).thenReturn(label);
+        return new InputMethodInfo(resolveInfo, /* isAuxIme */false,
+                DUMMY_SETTINGS_ACTIVITY,  /* subtypes */null, /* isDefaultResId */
+                1, /*forceDefault*/false);
+    }
+
+    private static ShadowInputMethodManager getShadowInputMethodManager(Context context) {
+        return Shadow.extract(context.getSystemService(Context.INPUT_METHOD_SERVICE));
+    }
+
+    private static ShadowDevicePolicyManager getShadowDevicePolicyManager(Context context) {
+        return Shadow.extract(context.getSystemService(Context.DEVICE_POLICY_SERVICE));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/language/LanguageBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/language/LanguageBasePreferenceControllerTest.java
new file mode 100644
index 0000000..8ce0c68
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/language/LanguageBasePreferenceControllerTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowLocalePicker;
+import com.android.car.settings.testutils.ShadowLocaleStore;
+import com.android.internal.app.LocaleStore;
+import com.android.internal.app.SuggestedLocaleAdapter;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Locale;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowLocalePicker.class, ShadowLocaleStore.class})
+public class LanguageBasePreferenceControllerTest {
+
+    private static final LocaleStore.LocaleInfo TEST_LOCALE_INFO = LocaleStore.getLocaleInfo(
+            Locale.FRENCH);
+    private static final Locale HAS_MULTIPLE_CHILD_LOCALE = Locale.ENGLISH;
+    private static final Locale HAS_CHILD_LOCALE = Locale.KOREAN;
+    private static final Locale NO_CHILD_LOCALE = Locale.FRANCE;
+
+    private static class TestLanguageBasePreferenceController extends
+            LanguageBasePreferenceController {
+
+        private SuggestedLocaleAdapter mAdapter;
+
+        TestLanguageBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        public void setAdapter(SuggestedLocaleAdapter adapter) {
+            mAdapter = adapter;
+        }
+
+        @Override
+        protected LocalePreferenceProvider defineLocaleProvider() {
+            return new LocalePreferenceProvider(getContext(), mAdapter);
+        }
+    }
+
+    private TestLanguageBasePreferenceController mController;
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private FragmentController mFragmentController;
+    @Mock
+    private SuggestedLocaleAdapter mSuggestedLocaleAdapter;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        PreferenceControllerTestHelper<TestLanguageBasePreferenceController>
+                preferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestLanguageBasePreferenceController.class, mPreferenceGroup);
+        mController = preferenceControllerHelper.getController();
+        mController.setAdapter(mSuggestedLocaleAdapter);
+        mFragmentController = preferenceControllerHelper.getMockFragmentController();
+        preferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        // Note that ENGLISH has 2 child locales.
+        ShadowLocaleStore.addLocaleRelationship(Locale.ENGLISH, Locale.CANADA);
+        ShadowLocaleStore.addLocaleRelationship(Locale.ENGLISH, Locale.US);
+
+        // Note that KOREAN has 1 child locale.
+        ShadowLocaleStore.addLocaleRelationship(Locale.KOREAN, Locale.KOREA);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowLocaleStore.reset();
+        ShadowLocalePicker.reset();
+    }
+
+    @Test
+    public void testRefreshUi_groupConstructed() {
+        when(mSuggestedLocaleAdapter.getCount()).thenReturn(1);
+        when(mSuggestedLocaleAdapter.getItemViewType(0)).thenReturn(
+                LocalePreferenceProvider.TYPE_LOCALE);
+        when(mSuggestedLocaleAdapter.getItem(0)).thenReturn(TEST_LOCALE_INFO);
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testOnPreferenceClick_noLocale_returnsFalse() {
+        assertThat(mController.onPreferenceClick(new Preference(mContext))).isFalse();
+    }
+
+    @Test
+    public void testOnPreferenceClick_hasMultipleChildLocales_returnsTrue() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(HAS_MULTIPLE_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        assertThat(mController.onPreferenceClick(preference)).isTrue();
+    }
+
+    @Test
+    public void testOnPreferenceClick_hasMultipleChildLocales_localeNotUpdated() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(HAS_MULTIPLE_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        mController.onPreferenceClick(preference);
+        assertThat(ShadowLocalePicker.localeWasUpdated()).isFalse();
+    }
+
+    @Test
+    public void testOnPreferenceClick_hasMultipleChildLocales_neverCallsGoBack() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(HAS_MULTIPLE_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        mController.onPreferenceClick(preference);
+        verify(mFragmentController, never()).goBack();
+    }
+
+    @Test
+    public void testOnPreferenceClick_hasSingleChildLocale_returnsTrue() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(HAS_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        assertThat(mController.onPreferenceClick(preference)).isTrue();
+    }
+
+    @Test
+    public void testOnPreferenceClick_hasSingleChildLocale_localeUpdated() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(HAS_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        mController.onPreferenceClick(preference);
+        assertThat(ShadowLocalePicker.localeWasUpdated()).isTrue();
+    }
+
+    @Test
+    public void testOnPreferenceClick_hasSingleChildLocale_callsGoBack() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(HAS_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        mController.onPreferenceClick(preference);
+        verify(mFragmentController).goBack();
+    }
+
+    @Test
+    public void testOnPreferenceClick_noChildLocale_returnsTrue() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(NO_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        assertThat(mController.onPreferenceClick(preference)).isTrue();
+    }
+
+    @Test
+    public void testOnPreferenceClick_noChildLocale_localeUpdated() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(NO_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        mController.onPreferenceClick(preference);
+        assertThat(ShadowLocalePicker.localeWasUpdated()).isTrue();
+    }
+
+    @Test
+    public void testOnPreferenceClick_noChildLocale_callsGoBack() {
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(NO_CHILD_LOCALE);
+        Preference preference = new Preference(mContext);
+        LocaleUtil.setLocaleArgument(preference, localeInfo);
+        mController.onPreferenceClick(preference);
+        verify(mFragmentController).goBack();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/language/LocalePreferenceProviderTest.java b/tests/robotests/src/com/android/car/settings/language/LocalePreferenceProviderTest.java
new file mode 100644
index 0000000..5847603
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/language/LocalePreferenceProviderTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2018 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.car.settings.language;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settingslib.R;
+import com.android.internal.app.LocaleStore;
+import com.android.internal.app.SuggestedLocaleAdapter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class LocalePreferenceProviderTest {
+
+    private static class Pair {
+        int mItemType;
+        LocaleStore.LocaleInfo mLocaleInfo;
+
+        Pair(int itemType, LocaleStore.LocaleInfo localeInfo) {
+            mItemType = itemType;
+            mLocaleInfo = localeInfo;
+        }
+    }
+
+    private Context mContext;
+    private LocalePreferenceProvider mLocalePreferenceProvider;
+    private LogicalPreferenceGroup mPreferenceGroup;
+    // This list includes the expected values that should be returned by the SuggestedLocaleAdapter.
+    // The index i in this list represents position, the itemType represents the return value for
+    // getItemViewType given the index i, and mLocaleInfo represents the return value for getItem
+    // given the index i.
+    private List<Pair> mLocaleAdapterExpectedValues;
+    @Mock
+    private SuggestedLocaleAdapter mSuggestedLocaleAdapter;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mLocalePreferenceProvider = new LocalePreferenceProvider(mContext, mSuggestedLocaleAdapter);
+        mLocaleAdapterExpectedValues = new ArrayList<>();
+
+        // LogicalPreferenceGroup needs to be part of a PreferenceScreen in order for it to add
+        // additional preferences.
+        PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        screen.addPreference(mPreferenceGroup);
+    }
+
+    @Test
+    public void testPopulateBasePreference_noSubSections() {
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.US)));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.UK)));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.CANADA)));
+        prepareSuggestedLocaleAdapterMock();
+
+        mLocalePreferenceProvider.populateBasePreference(mPreferenceGroup, mock(
+                Preference.OnPreferenceClickListener.class));
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(3);
+    }
+
+    @Test
+    public void testPopulateBasePreference_withSubSections() {
+        mLocaleAdapterExpectedValues.add(
+                new Pair(LocalePreferenceProvider.TYPE_HEADER_SUGGESTED, null));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.US)));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.FRANCE)));
+        mLocaleAdapterExpectedValues.add(
+                new Pair(LocalePreferenceProvider.TYPE_HEADER_ALL_OTHERS, null));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.UK)));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.CANADA)));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.KOREA)));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.CHINA)));
+        prepareSuggestedLocaleAdapterMock();
+
+        mLocalePreferenceProvider.populateBasePreference(mPreferenceGroup, mock(
+                Preference.OnPreferenceClickListener.class));
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        PreferenceCategory firstCategory = (PreferenceCategory) mPreferenceGroup.getPreference(0);
+        assertThat(firstCategory.getTitle()).isEqualTo(
+                mContext.getString(R.string.language_picker_list_suggested_header));
+        assertThat(firstCategory.getPreferenceCount()).isEqualTo(2);
+
+        PreferenceCategory secondCategory = (PreferenceCategory) mPreferenceGroup.getPreference(1);
+        assertThat(secondCategory.getTitle()).isEqualTo(
+                mContext.getString(R.string.language_picker_list_all_header));
+        assertThat(secondCategory.getPreferenceCount()).isEqualTo(4);
+    }
+
+    @Test
+    public void testClickListenerTriggered() {
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.US)));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.UK)));
+        mLocaleAdapterExpectedValues.add(new Pair(LocalePreferenceProvider.TYPE_LOCALE,
+                LocaleStore.getLocaleInfo(Locale.CANADA)));
+        prepareSuggestedLocaleAdapterMock();
+
+        Preference.OnPreferenceClickListener listener = mock(
+                Preference.OnPreferenceClickListener.class);
+        mLocalePreferenceProvider.populateBasePreference(mPreferenceGroup, listener);
+
+        mPreferenceGroup.getPreference(0).performClick();
+        verify(listener).onPreferenceClick(mPreferenceGroup.getPreference(0));
+        mPreferenceGroup.getPreference(1).performClick();
+        verify(listener).onPreferenceClick(mPreferenceGroup.getPreference(1));
+        mPreferenceGroup.getPreference(2).performClick();
+        verify(listener).onPreferenceClick(mPreferenceGroup.getPreference(2));
+    }
+
+    private void prepareSuggestedLocaleAdapterMock() {
+        for (int i = 0; i < mLocaleAdapterExpectedValues.size(); i++) {
+            Pair entry = mLocaleAdapterExpectedValues.get(i);
+            int itemType = entry.mItemType;
+            LocaleStore.LocaleInfo localeInfo = entry.mLocaleInfo;
+
+            when(mSuggestedLocaleAdapter.getItemViewType(i)).thenReturn(itemType);
+            when(mSuggestedLocaleAdapter.getItem(i)).thenReturn(localeInfo);
+        }
+
+        when(mSuggestedLocaleAdapter.getCount()).thenReturn(mLocaleAdapterExpectedValues.size());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/location/BluetoothScanningPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/location/BluetoothScanningPreferenceControllerTest.java
new file mode 100644
index 0000000..abb822b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/location/BluetoothScanningPreferenceControllerTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class BluetoothScanningPreferenceControllerTest {
+
+    private Context mContext;
+    private SwitchPreference mPreference;
+    private BluetoothScanningPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        PreferenceControllerTestHelper<BluetoothScanningPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        BluetoothScanningPreferenceController.class, mPreference);
+        mController = controllerHelper.getController();
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH_LE, /* supported= */ true);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hasBluetoothFeature_available() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH_LE, /* supported= */ true);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noBluetoothFeature_unsupported() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH_LE, /* supported= */ false);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void refreshUi_bluetoothScanningEnabled_shouldCheckPreference() {
+        mPreference.setChecked(false);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 1);
+        mController.refreshUi();
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_bluetoothScanningDisabled_shouldUncheckPreference() {
+        mPreference.setChecked(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
+        mController.refreshUi();
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void handlePreferenceChanged_preferenceChecked_shouldEnableBluetoothScanning() {
+        mPreference.callChangeListener(true);
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0)).isEqualTo(1);
+    }
+
+    @Test
+    public void handlePreferenceChanged_preferenceUnchecked_shouldDisableBluetoothScanning() {
+        mPreference.callChangeListener(false);
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 1)).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/location/LocationFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/location/LocationFooterPreferenceControllerTest.java
new file mode 100644
index 0000000..344df59
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/location/LocationFooterPreferenceControllerTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.location.LocationManager;
+import android.os.Bundle;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class LocationFooterPreferenceControllerTest {
+    private static final String TEST_TEXT = "sample text";
+    private static final int TEST_RES_ID = 1024;
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private Resources mResources;
+
+    private PreferenceControllerTestHelper<LocationFooterPreferenceController> mControllerHelper;
+    private LocationFooterPreferenceController mController;
+    private PreferenceGroup mGroup;
+    private List<ResolveInfo> mResolveInfos;
+
+    @Before
+    public void setUp() throws PackageManager.NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        mGroup = new LogicalPreferenceGroup(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                LocationFooterPreferenceController.class, mGroup);
+        mController = mControllerHelper.getController();
+        mController.setPackageManager(mPackageManager);
+
+        mResolveInfos = new ArrayList<>();
+        when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt()))
+                .thenReturn(mResolveInfos);
+        when(mPackageManager.getResourcesForApplication(any(ApplicationInfo.class)))
+                .thenReturn(mResources);
+        when(mResources.getString(TEST_RES_ID)).thenReturn(TEST_TEXT);
+    }
+
+    // Visibility Tests.
+    @Test
+    public void footer_isVisibleWhenThereAreValidInjections() {
+        mResolveInfos.add(
+                getTestResolveInfo(/* isSystemApp= */ true, /* hasRequiredMetadata= */ true));
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mGroup.isVisible()).isTrue();
+    }
+
+    @Test
+    public void footer_isHiddenWhenThereAreNoValidInjections_notSystemApp() {
+        mResolveInfos.add(
+                getTestResolveInfo(/* isSystemApp= */ false, /* hasRequiredMetadata= */ true));
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mGroup.isVisible()).isFalse();
+    }
+
+    @Test
+    public void footer_isHiddenWhenThereAreNoValidInjections_noMetaData() {
+        mResolveInfos.add(
+                getTestResolveInfo(/* isSystemApp= */ true, /* hasRequiredMetadata= */ false));
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mGroup.isVisible()).isFalse();
+    }
+
+    // Correctness Tests.
+    @Test
+    public void onCreate_addsInjectedFooterToGroup() {
+        int numFooters = 3;
+        for (int i = 0; i < numFooters; i++) {
+            mResolveInfos.add(
+                    getTestResolveInfo(/* isSystemApp= */ true, /* hasRequiredMetadata= */ true));
+        }
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mGroup.getPreferenceCount()).isEqualTo(numFooters);
+    }
+
+    @Test
+    public void onCreate_injectedFooterHasCorrectText() {
+        mResolveInfos.add(
+                getTestResolveInfo(/* isSystemApp= */ true, /* hasRequiredMetadata= */ true));
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mGroup.getPreference(0).getSummary()).isEqualTo(TEST_TEXT);
+    }
+
+    // Broadcast Tests.
+    @Test
+    public void onCreate_broadcastsFooterDisplayedIntentForValidInjections() {
+        ResolveInfo testResolveInfo =
+                getTestResolveInfo(/* isSystemApp= */ true, /* hasRequiredMetadata= */ true);
+        mResolveInfos.add(testResolveInfo);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired).hasSize(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(
+                LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION);
+        assertThat(intentFired.getComponent()).isEqualTo(testResolveInfo
+                .getComponentInfo().getComponentName());
+    }
+
+    @Test
+    public void onCreate_doesNotBroadcastFooterDisplayedIntentIfNoValidInjections() {
+        mResolveInfos.add(
+                getTestResolveInfo(/* isSystemApp= */ false, /* hasRequiredMetadata= */ true));
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired).isEmpty();
+    }
+
+    @Test
+    public void onStop_broadcastsFooterRemovedIntent() {
+        mResolveInfos.add(
+                getTestResolveInfo(/* isSystemApp= */ true, /* hasRequiredMetadata= */ true));
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired).hasSize(2);
+        Intent intentFired = intentsFired.get(1);
+        assertThat(intentFired.getAction()).isEqualTo(
+                LocationManager.SETTINGS_FOOTER_REMOVED_ACTION);
+    }
+
+    /**
+     * Returns a ResolveInfo object for testing.
+     *
+     * <p>Injections are only valid if they are both a system app, and have the required METADATA.
+     *
+     * @param isSystemApp         true if the application is a system app.
+     * @param hasRequiredMetaData true if the broadcast receiver has a valid value for
+     *                            {@link LocationManager#METADATA_SETTINGS_FOOTER_STRING}
+     */
+    private ResolveInfo getTestResolveInfo(boolean isSystemApp, boolean hasRequiredMetaData) {
+        ResolveInfo testResolveInfo = new ResolveInfo();
+        ApplicationInfo testAppInfo = new ApplicationInfo();
+        if (isSystemApp) {
+            testAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        }
+        ActivityInfo testActivityInfo = new ActivityInfo();
+        testActivityInfo.name = "TestActivityName";
+        testActivityInfo.packageName = "TestPackageName";
+        testActivityInfo.applicationInfo = testAppInfo;
+        if (hasRequiredMetaData) {
+            testActivityInfo.metaData = new Bundle();
+            testActivityInfo.metaData.putInt(
+                    LocationManager.METADATA_SETTINGS_FOOTER_STRING, TEST_RES_ID);
+        }
+        testResolveInfo.activityInfo = testActivityInfo;
+        return testResolveInfo;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/location/LocationScanningPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/location/LocationScanningPreferenceControllerTest.java
new file mode 100644
index 0000000..15a9716
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/location/LocationScanningPreferenceControllerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowPackageManager;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class LocationScanningPreferenceControllerTest {
+
+    private ShadowPackageManager mShadowPackageManager;
+    private LocationScanningPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        Context context = RuntimeEnvironment.application;
+        PreferenceControllerTestHelper<LocationScanningPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(context,
+                        LocationScanningPreferenceController.class, new Preference(context));
+        mController = controllerHelper.getController();
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mShadowPackageManager = Shadows.shadowOf(context.getPackageManager());
+    }
+
+    @Test
+    public void getAvailabilityStatus_available_wifiOrBluetooth() {
+        mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_WIFI, /* supported= */ true);
+        mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE,
+                /* supported= */ false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+
+        mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_WIFI, /* supported= */ false);
+        mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE,
+                /* supported= */ true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_notAvailable_noWifiNoBluetooth() {
+        mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_WIFI, /* supported= */ false);
+        mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE,
+                /* supported= */ false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/location/LocationServicesPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/location/LocationServicesPreferenceControllerTest.java
new file mode 100644
index 0000000..500919c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/location/LocationServicesPreferenceControllerTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.location.SettingInjectorService;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.location.SettingsInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class LocationServicesPreferenceControllerTest {
+    @Mock
+    private SettingsInjector mSettingsInjector;
+    private Context mContext;
+    private PreferenceControllerTestHelper<LocationServicesPreferenceController> mControllerHelper;
+    private LocationServicesPreferenceController mController;
+    private PreferenceGroup mCategory;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mCategory = new PreferenceCategory(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                LocationServicesPreferenceController.class, mCategory);
+        mController = mControllerHelper.getController();
+        mController.setSettingsInjector(mSettingsInjector);
+    }
+
+    @Test
+    public void onCreate_addsInjectedSettingsToPreferenceCategory() {
+        List<Preference> samplePrefs = getSamplePreferences();
+        when(mSettingsInjector.hasInjectedSettings(anyInt())).thenReturn(true);
+        doReturn(samplePrefs).when(mSettingsInjector)
+                .getInjectedSettings(any(Context.class), anyInt());
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mCategory.getPreferenceCount()).isEqualTo(samplePrefs.size());
+    }
+
+    @Test
+    public void onStart_registersBroadcastReceiver() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mContext.sendBroadcast(new Intent(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED));
+        verify(mSettingsInjector).reloadStatusMessages();
+    }
+
+    @Test
+    public void onStop_unregistersBroadcastReceiver() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mContext.sendBroadcast(new Intent(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED));
+        verify(mSettingsInjector).reloadStatusMessages();
+
+        clearInvocations(mSettingsInjector);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mContext.sendBroadcast(new Intent(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED));
+        verify(mSettingsInjector, never()).reloadStatusMessages();
+    }
+
+    @Test
+    public void preferenceCategory_isVisibleIfThereAreInjectedSettings() {
+        doReturn(true).when(mSettingsInjector).hasInjectedSettings(anyInt());
+        doReturn(getSamplePreferences()).when(mSettingsInjector)
+                .getInjectedSettings(any(Context.class), anyInt());
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mCategory.isVisible()).isTrue();
+    }
+
+    @Test
+    public void preferenceCategory_isHiddenIfThereAreNoInjectedSettings() {
+        doReturn(false).when(mSettingsInjector).hasInjectedSettings(anyInt());
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mCategory.isVisible()).isFalse();
+    }
+
+    private List<Preference> getSamplePreferences() {
+        return new ArrayList<>(Arrays.asList(
+                new Preference(mContext), new Preference(mContext), new Preference(mContext)));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/location/LocationSettingsFragmentTest.java b/tests/robotests/src/com/android/car/settings/location/LocationSettingsFragmentTest.java
new file mode 100644
index 0000000..3739852
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/location/LocationSettingsFragmentTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Service;
+import android.content.Intent;
+import android.location.LocationManager;
+import android.widget.Switch;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowLocationManager;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class, ShadowLocationManager.class})
+public class LocationSettingsFragmentTest {
+    private BaseTestActivity mActivity;
+    private LocationManager mLocationManager;
+    private Switch mLocationSwitch;
+
+    @Before
+    public void setUp() {
+        mLocationManager = (LocationManager) RuntimeEnvironment.application
+                .getSystemService(Service.LOCATION_SERVICE);
+
+        mActivity = Robolectric.setupActivity(BaseTestActivity.class);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSecureSettings.reset();
+    }
+
+    @Test
+    public void locationSwitch_toggle_shouldBroadcastLocationModeChangedIntent() {
+        initFragment();
+        mLocationSwitch.setChecked(!mLocationSwitch.isChecked());
+
+        List<Intent> intentsFired = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(intentsFired).hasSize(1);
+        Intent intentFired = intentsFired.get(0);
+        assertThat(intentFired.getAction()).isEqualTo(LocationManager.MODE_CHANGED_ACTION);
+    }
+
+    @Test
+    public void locationSwitch_checked_enablesLocation() {
+        initFragment();
+        mLocationSwitch.setChecked(true);
+
+        assertThat(mLocationManager.isLocationEnabled()).isTrue();
+    }
+
+    @Test
+    public void locationSwitch_unchecked_disablesLocation() {
+        initFragment();
+        mLocationSwitch.setChecked(false);
+
+        assertThat(mLocationManager.isLocationEnabled()).isFalse();
+    }
+
+    private void initFragment() {
+        mActivity.launchFragment(new LocationSettingsFragment());
+        mLocationSwitch = (Switch) mActivity.findViewById(R.id.toggle_switch);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/location/RecentLocationRequestsEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/location/RecentLocationRequestsEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..6888f0d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/location/RecentLocationRequestsEntryPreferenceControllerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowLocationManager;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+import com.android.settingslib.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class, ShadowLocationManager.class})
+public class RecentLocationRequestsEntryPreferenceControllerTest {
+
+    private RecentLocationRequestsEntryPreferenceController mController;
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        Context context = RuntimeEnvironment.application;
+        mPreference = new Preference(context);
+        PreferenceControllerTestHelper<RecentLocationRequestsEntryPreferenceController>
+                controllerHelper = new PreferenceControllerTestHelper<>(context,
+                RecentLocationRequestsEntryPreferenceController.class, mPreference);
+        mController = controllerHelper.getController();
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSecureSettings.reset();
+    }
+
+    @Test
+    public void refreshUi_locationOn_preferenceIsEnabled() {
+        setLocationEnabled(true);
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_locationOff_preferenceIsDisabled() {
+        setLocationEnabled(false);
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void locationModeChangedBroadcastSent_locationOff_preferenceIsDisabled() {
+        setLocationEnabled(true);
+        mController.refreshUi();
+        setLocationEnabled(false);
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void locationModeChangedBroadcastSent_locationOn_preferenceIsEnabled() {
+        setLocationEnabled(false);
+        mController.refreshUi();
+        setLocationEnabled(true);
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    private void setLocationEnabled(boolean enabled) {
+        Utils.updateLocationEnabled(RuntimeEnvironment.application, enabled, UserHandle.myUserId(),
+                Settings.Secure.LOCATION_CHANGER_SYSTEM_SETTINGS);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/location/RecentLocationRequestsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/location/RecentLocationRequestsPreferenceControllerTest.java
new file mode 100644
index 0000000..aa638cc
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/location/RecentLocationRequestsPreferenceControllerTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.location.RecentLocationApps;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class RecentLocationRequestsPreferenceControllerTest {
+
+    @Mock
+    private RecentLocationApps mRecentLocationApps;
+
+    private RecentLocationRequestsPreferenceController mController;
+    private PreferenceScreen mScreen;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+        PreferenceControllerTestHelper<RecentLocationRequestsPreferenceController>
+                controllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                RecentLocationRequestsPreferenceController.class, mScreen);
+        mController = controllerHelper.getController();
+        mController.setRecentLocationApps(mRecentLocationApps);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void refreshUi_noRecentRequests_messageDisplayed() {
+        when(mRecentLocationApps.getAppListSorted()).thenReturn(Collections.emptyList());
+        mController.refreshUi();
+
+        assertThat(mScreen.getPreference(0).getTitle()).isEqualTo(
+                mContext.getString(R.string.location_settings_recent_requests_empty_message));
+    }
+
+    @Test
+    public void refreshUi_someRecentRequests_preferencesAddedToScreen() {
+        List<RecentLocationApps.Request> list = Arrays.asList(
+                mock(RecentLocationApps.Request.class),
+                mock(RecentLocationApps.Request.class),
+                mock(RecentLocationApps.Request.class));
+        when(mRecentLocationApps.getAppListSorted()).thenReturn(list);
+        mController.refreshUi();
+
+        assertThat(mScreen.getPreferenceCount()).isEqualTo(list.size());
+    }
+
+    @Test
+    public void refreshUi_newRecentRequests_listIsUpdated() {
+        List<RecentLocationApps.Request> list1 = Arrays.asList(
+                mock(RecentLocationApps.Request.class),
+                mock(RecentLocationApps.Request.class),
+                mock(RecentLocationApps.Request.class));
+        when(mRecentLocationApps.getAppListSorted()).thenReturn(list1);
+
+        List<RecentLocationApps.Request> list2 = new ArrayList<>(list1);
+        list2.add(mock(RecentLocationApps.Request.class));
+
+        mController.refreshUi();
+        assertThat(mScreen.getPreferenceCount()).isEqualTo(list1.size());
+
+        when(mRecentLocationApps.getAppListSorted()).thenReturn(list2);
+        mController.refreshUi();
+
+        assertThat(mScreen.getPreferenceCount()).isEqualTo(list2.size());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/location/WifiScanningPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/location/WifiScanningPreferenceControllerTest.java
new file mode 100644
index 0000000..b97887d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/location/WifiScanningPreferenceControllerTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.car.settings.location;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiScanningPreferenceControllerTest {
+
+    private Context mContext;
+    private SwitchPreference mPreference;
+    private WifiScanningPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        PreferenceControllerTestHelper<WifiScanningPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        WifiScanningPreferenceController.class, mPreference);
+        mController = controllerHelper.getController();
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ true);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hasWifiFeature_available() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ true);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noWifiFeature_unsupported() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ false);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void refreshUi_wifiScanningEnabled_shouldCheckPreference() {
+        mPreference.setChecked(false);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1);
+        mController.refreshUi();
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_wifiScanningDisabled_shouldUncheckPreference() {
+        mPreference.setChecked(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
+        mController.refreshUi();
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void handlePreferenceChanged_preferenceChecked_shouldEnableWifiScanning() {
+        mPreference.callChangeListener(true);
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0)).isEqualTo(1);
+    }
+
+    @Test
+    public void handlePreferenceChanged_preferenceUnchecked_shouldDisableWifiScanning() {
+        mPreference.callChangeListener(false);
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1)).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/network/ConfirmMobileDataDisableDialogTest.java b/tests/robotests/src/com/android/car/settings/network/ConfirmMobileDataDisableDialogTest.java
new file mode 100644
index 0000000..8cd7370
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/network/ConfirmMobileDataDisableDialogTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.shadows.ShadowDialog;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ConfirmMobileDataDisableDialogTest {
+
+    private ConfirmMobileDataDisableDialog mDialog;
+    @Mock
+    private ConfirmMobileDataDisableDialog.ConfirmMobileDataDisableListener mListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDialog = new ConfirmMobileDataDisableDialog();
+        mDialog.setListener(mListener);
+    }
+
+    @Test
+    public void confirmDisable_disableCallbackCalled() {
+        AlertDialog dialog = showDialog(mDialog);
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        verify(mListener).onMobileDataDisableConfirmed();
+    }
+
+    @Test
+    public void rejectDisable_rejectCallbackCalled() {
+        AlertDialog dialog = showDialog(mDialog);
+        dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
+
+        verify(mListener).onMobileDataDisableRejected();
+    }
+
+    private AlertDialog showDialog(ConfirmMobileDataDisableDialog fragment) {
+        BaseTestActivity activity = Robolectric.setupActivity(BaseTestActivity.class);
+        activity.showDialog(fragment, /* tag= */ null);
+        return (AlertDialog) ShadowDialog.getLatestDialog();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/network/MobileDataTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/network/MobileDataTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..7329d79
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/network/MobileDataTogglePreferenceControllerTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowTelephonyManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowTelephonyManager.class})
+public class MobileDataTogglePreferenceControllerTest {
+
+    private Context mContext;
+    private TwoStatePreference mPreference;
+    private PreferenceControllerTestHelper<MobileDataTogglePreferenceController>
+            mControllerHelper;
+    private MobileDataTogglePreferenceController mController;
+    private TelephonyManager mTelephonyManager;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SwitchPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                MobileDataTogglePreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+
+        mTelephonyManager.setDataEnabled(false);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowTelephonyManager.reset();
+    }
+
+    @Test
+    public void refreshUi_dataEnabled_setChecked() {
+        mTelephonyManager.setDataEnabled(true);
+        mController.refreshUi();
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_dataDisabled_setUnchecked() {
+        mTelephonyManager.setDataEnabled(false);
+        mController.refreshUi();
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void handlePreferenceChanged_setFalse_opensDialog() {
+        mPreference.callChangeListener(false);
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmMobileDataDisableDialog.class), eq(ConfirmMobileDataDisableDialog.TAG));
+    }
+
+    @Test
+    public void handlePreferenceChanged_setTrue_enablesData() {
+        mPreference.callChangeListener(true);
+
+        assertThat(mTelephonyManager.isDataEnabled()).isTrue();
+    }
+
+    @Test
+    public void handlePreferenceChanged_setTrue_checksSwitch() {
+        mPreference.setChecked(false);
+        mPreference.callChangeListener(true);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onMobileDataDisableConfirmed_unchecksSwitch() {
+        mTelephonyManager.setDataEnabled(true);
+        mPreference.setChecked(true);
+
+        mController.onMobileDataDisableConfirmed();
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onMobileDataDisableConfirmed_disablesMobileData() {
+        mTelephonyManager.setDataEnabled(true);
+
+        mController.onMobileDataDisableConfirmed();
+
+        assertThat(mTelephonyManager.isDataEnabled()).isFalse();
+    }
+
+    @Test
+    public void onMobileDataDisableRejected_checksSwitch() {
+        mTelephonyManager.setDataEnabled(true);
+        mPreference.setChecked(false);
+
+        mController.onMobileDataDisableRejected();
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/network/MobileNetworkEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/network/MobileNetworkEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..9e8ac60
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/network/MobileNetworkEntryPreferenceControllerTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.shadow.api.Shadow.extract;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowConnectivityManager;
+import com.android.car.settings.testutils.ShadowTelephonyManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowNetwork;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowConnectivityManager.class,
+        ShadowTelephonyManager.class})
+public class MobileNetworkEntryPreferenceControllerTest {
+
+    private static final String TEST_NETWORK_NAME = "test network name";
+    private static final UserInfo TEST_ADMIN_USER = new UserInfo(10, "test_name",
+            UserInfo.FLAG_ADMIN);
+    private static final UserInfo TEST_NON_ADMIN_USER = new UserInfo(10, "test_name",
+            /* flags= */ 0);
+
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<MobileNetworkEntryPreferenceController>
+            mControllerHelper;
+    private MobileNetworkEntryPreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private NetworkCapabilities mNetworkCapabilities;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                MobileNetworkEntryPreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+
+        // Setup to always make preference available.
+        getShadowConnectivityManager().clearAllNetworks();
+        getShadowConnectivityManager().addNetworkCapabilities(
+                ShadowNetwork.newInstance(ConnectivityManager.TYPE_MOBILE), mNetworkCapabilities);
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(
+                true);
+        when(mCarUserManagerHelper.getCurrentProcessUserInfo()).thenReturn(TEST_ADMIN_USER);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)).thenReturn(false);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowConnectivityManager.reset();
+        ShadowTelephonyManager.reset();
+    }
+
+    @Test
+    public void onStart_phoneStateListenerSet() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(getShadowTelephonyManager().getListenersForFlags(
+                PhoneStateListener.LISTEN_SERVICE_STATE).size()).isEqualTo(1);
+    }
+
+    @Test
+    public void onStop_phoneStateListenerUnset() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(getShadowTelephonyManager().getListenersForFlags(
+                PhoneStateListener.LISTEN_SERVICE_STATE).size()).isEqualTo(1);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+        assertThat(getShadowTelephonyManager().getListenersForFlags(
+                PhoneStateListener.LISTEN_SERVICE_STATE).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void onStart_airplaneModeChangedListenerSet() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        // One receiver (bluetooth pairing request) is always registered through the manifest.
+        assertThat(ShadowApplication.getInstance().getRegisteredReceivers().size()).isGreaterThan(
+                0);
+
+        boolean hasMatch = false;
+        for (ShadowApplication.Wrapper wrapper :
+                ShadowApplication.getInstance().getRegisteredReceivers()) {
+            if (wrapper.getIntentFilter().getAction(0) == Intent.ACTION_AIRPLANE_MODE_CHANGED) {
+                hasMatch = true;
+            }
+        }
+        assertThat(hasMatch).isTrue();
+    }
+
+    @Test
+    public void onStop_airplaneModeChangedListenerUnset() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        int prevSize = ShadowApplication.getInstance().getRegisteredReceivers().size();
+        assertThat(prevSize).isGreaterThan(0);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+        assertThat(ShadowApplication.getInstance().getRegisteredReceivers().size()).isLessThan(
+                prevSize);
+
+        boolean hasMatch = false;
+        for (ShadowApplication.Wrapper wrapper :
+                ShadowApplication.getInstance().getRegisteredReceivers()) {
+            if (wrapper.getIntentFilter().getAction(0) == Intent.ACTION_AIRPLANE_MODE_CHANGED) {
+                hasMatch = true;
+            }
+        }
+        assertThat(hasMatch).isFalse();
+    }
+
+    @Test
+    public void getAvailabilityStatus_noMobileNetwork_unsupported() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(
+                false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_notAdmin_disabledForUser() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(
+                true);
+        when(mCarUserManagerHelper.getCurrentProcessUserInfo()).thenReturn(TEST_NON_ADMIN_USER);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hasRestriction_disabledForUser() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(
+                true);
+        when(mCarUserManagerHelper.getCurrentProcessUserInfo()).thenReturn(TEST_ADMIN_USER);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hasMobileNetwork_isAdmin_noRestriction_available() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(
+                true);
+        when(mCarUserManagerHelper.getCurrentProcessUserInfo()).thenReturn(TEST_ADMIN_USER);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void refreshUi_airplaneModeOn_preferenceDisabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_airplaneModeOff_preferenceEnabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_summarySet() {
+        getShadowTelephonyManager().setNetworkOperatorName(TEST_NETWORK_NAME);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(TEST_NETWORK_NAME);
+    }
+
+    @Test
+    public void sendBroadcast_airplaneModeOn_disablePreference() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mPreference.setEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        mContext.sendBroadcast(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void sendBroadcast_airplaneModeOff_enablePreference() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mPreference.setEnabled(false);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+        mContext.sendBroadcast(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    private ShadowTelephonyManager getShadowTelephonyManager() {
+        return (ShadowTelephonyManager) extract(mContext.getSystemService(TelephonyManager.class));
+    }
+
+    private ShadowConnectivityManager getShadowConnectivityManager() {
+        return (ShadowConnectivityManager) extract(
+                mContext.getSystemService(ConnectivityManager.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/network/NetworkUtilsTest.java b/tests/robotests/src/com/android/car/settings/network/NetworkUtilsTest.java
new file mode 100644
index 0000000..99687b9
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/network/NetworkUtilsTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.network;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.telephony.TelephonyManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class NetworkUtilsTest {
+
+    @Test
+    public void hasMobileNetwork_hasCellularCapabilities_returnsTrue() {
+        ConnectivityManager connectivityManager = mock(ConnectivityManager.class);
+        Network network = mock(Network.class);
+        NetworkCapabilities capabilities = mock(NetworkCapabilities.class);
+
+        when(connectivityManager.getAllNetworks()).thenReturn(new Network[]{network});
+        when(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities);
+        when(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(true);
+
+        assertThat(NetworkUtils.hasMobileNetwork(connectivityManager)).isTrue();
+    }
+
+    @Test
+    public void hasMobileNetwork_hasNoCellularCapabilities_returnsFalse() {
+        ConnectivityManager connectivityManager = mock(ConnectivityManager.class);
+        Network network = mock(Network.class);
+        NetworkCapabilities capabilities = mock(NetworkCapabilities.class);
+
+        when(connectivityManager.getAllNetworks()).thenReturn(new Network[]{network});
+        when(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities);
+        when(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(false);
+
+        assertThat(NetworkUtils.hasMobileNetwork(connectivityManager)).isFalse();
+    }
+
+    @Test
+    public void hasSim_simAbsent_returnsFalse() {
+        TelephonyManager telephonyManager = mock(TelephonyManager.class);
+        when(telephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_ABSENT);
+
+        assertThat(NetworkUtils.hasSim(telephonyManager)).isFalse();
+    }
+
+    @Test
+    public void hasSim_simUnknown_returnsFalse() {
+        TelephonyManager telephonyManager = mock(TelephonyManager.class);
+        when(telephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_UNKNOWN);
+
+        assertThat(NetworkUtils.hasSim(telephonyManager)).isFalse();
+    }
+
+    @Test
+    public void hasSim_otherStatus_returnsTrue() {
+        TelephonyManager telephonyManager = mock(TelephonyManager.class);
+        when(telephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_LOADED);
+
+        assertThat(NetworkUtils.hasSim(telephonyManager)).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/security/ChooseLockPatternFragmentTest.java b/tests/robotests/src/com/android/car/settings/security/ChooseLockPatternFragmentTest.java
index 295c040..db8bf9c 100644
--- a/tests/robotests/src/com/android/car/settings/security/ChooseLockPatternFragmentTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/ChooseLockPatternFragmentTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.verify;
 
 import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.FragmentController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -36,7 +37,7 @@
 
     @Before
     public void initFragment() {
-        mFragment = new ChooseLockPatternFragment();
+        mFragment = FragmentController.of(new ChooseLockPatternFragment()).setup();
     }
 
     /**
diff --git a/tests/robotests/src/com/android/car/settings/security/ChooseLockPinPasswordFragmentTest.java b/tests/robotests/src/com/android/car/settings/security/ChooseLockPinPasswordFragmentTest.java
index 1ecd784..de68c94 100644
--- a/tests/robotests/src/com/android/car/settings/security/ChooseLockPinPasswordFragmentTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/ChooseLockPinPasswordFragmentTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.verify;
 
 import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.FragmentController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -36,7 +37,8 @@
 
     @Before
     public void initFragment() {
-        mFragment = ChooseLockPinPasswordFragment.newPasswordInstance(/* isInSetupWizard= */ false);
+        mFragment = FragmentController.of(
+                ChooseLockPinPasswordFragment.newPasswordInstance()).setup();
     }
 
     /**
diff --git a/tests/robotests/src/com/android/car/settings/security/ConfirmLockPinPasswordFragmentTest.java b/tests/robotests/src/com/android/car/settings/security/ConfirmLockPinPasswordFragmentTest.java
index 5126a18..ad07dad 100644
--- a/tests/robotests/src/com/android/car/settings/security/ConfirmLockPinPasswordFragmentTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/ConfirmLockPinPasswordFragmentTest.java
@@ -22,7 +22,7 @@
 
 import com.android.car.settings.CarSettingsRobolectricTestRunner;
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
+import com.android.car.settings.common.FragmentController;
 import com.android.car.settings.testutils.BaseTestActivity;
 
 import org.junit.Before;
@@ -41,13 +41,9 @@
 
     @Before
     public void initFragment() {
-        mTestActivity = Robolectric.buildActivity(TestSettingsScreenLockActivity.class)
-                .create()
-                .start()
-                .resume()
-                .get();
+        mTestActivity = Robolectric.setupActivity(TestSettingsScreenLockActivity.class);
 
-        mPinFragment = ConfirmLockPinPasswordFragment.newPinInstance(/* isInSetupWizard= */ false);
+        mPinFragment = ConfirmLockPinPasswordFragment.newPinInstance();
         mTestActivity.launchFragment(mPinFragment);
     }
 
@@ -68,15 +64,18 @@
      * The containing activity of ConfirmLockPinPasswordFragment must implement two interfaces
      */
     private static class TestSettingsScreenLockActivity extends BaseTestActivity implements
-            CheckLockListener, BaseFragment.FragmentController {
+            CheckLockListener, FragmentController {
 
         @Override
-        public void onLockVerified(String lock) {}
+        public void onLockVerified(byte[] lock) {
+        }
 
         @Override
-        public void goBack() {}
+        public void goBack() {
+        }
 
         @Override
-        public void showDOBlockingMessage() {}
+        public void showBlockingMessage() {
+        }
     }
 }
diff --git a/tests/robotests/src/com/android/car/settings/security/ConfirmRemoveScreenLockDialogTest.java b/tests/robotests/src/com/android/car/settings/security/ConfirmRemoveScreenLockDialogTest.java
new file mode 100644
index 0000000..21e760a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/security/ConfirmRemoveScreenLockDialogTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.DialogTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ConfirmRemoveScreenLockDialogTest {
+
+    private static final String TEST_TAG = "test_dialog_tag";
+
+    private BaseTestActivity mTestActivity;
+    private ConfirmRemoveScreenLockDialog mDialog;
+    @Mock
+    private ConfirmRemoveScreenLockDialog.ConfirmRemoveScreenLockListener mListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mDialog = new ConfirmRemoveScreenLockDialog();
+        mDialog.setConfirmRemoveScreenLockListener(mListener);
+        mTestActivity = Robolectric.setupActivity(BaseTestActivity.class);
+        mTestActivity.showDialog(mDialog, TEST_TAG);
+    }
+
+    @Test
+    public void testInitialState_dialogShown() {
+        assertThat(isDialogShown()).isTrue();
+    }
+
+    @Test
+    public void testConfirmRemoveScreenLockListenerCalled_listenerCalled() {
+        DialogTestUtils.clickPositiveButton(mDialog);
+        verify(mListener).onConfirmRemoveScreenLock();
+    }
+
+    @Test
+    public void testConfirmRemoveScreenLockListenerCalled_dialogDismissed() {
+        DialogTestUtils.clickPositiveButton(mDialog);
+        assertThat(isDialogShown()).isFalse();
+    }
+
+    private boolean isDialogShown() {
+        return mTestActivity.findDialogByTag(TEST_TAG) != null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/security/InitialLockSetupServiceTest.java b/tests/robotests/src/com/android/car/settings/security/InitialLockSetupServiceTest.java
new file mode 100644
index 0000000..d05a33d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/security/InitialLockSetupServiceTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.RemoteException;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.setupservice.InitialLockSetupService;
+import com.android.car.settings.testutils.ShadowLockPatternUtils;
+import com.android.car.setupwizardlib.IInitialLockSetupService;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.LockTypes;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.SetLockCodes;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
+import com.android.car.setupwizardlib.InitialLockSetupHelper;
+import com.android.car.setupwizardlib.LockConfig;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContextWrapper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests that the {@link InitialLockSetupService} properly handles connections and lock requests.
+ */
+@Config(shadows = ShadowLockPatternUtils.class)
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class InitialLockSetupServiceTest {
+
+    private static final String LOCK_PERMISSION = "com.android.car.settings.SET_INITIAL_LOCK";
+
+    private InitialLockSetupService mInitialLockSetupService;
+    private Context mContext;
+
+    @Before
+    public void setupService() {
+        ShadowLockPatternUtils.reset();
+        mInitialLockSetupService = Robolectric.buildService(InitialLockSetupService.class)
+                .create()
+                .get();
+        mContext = RuntimeEnvironment.application;
+        ShadowContextWrapper shadowContextWrapper = Shadows.shadowOf((ContextWrapper) mContext);
+        shadowContextWrapper.grantPermissions(LOCK_PERMISSION);
+    }
+
+    @Test
+    public void testBindReturnsNull_ifLockSet() {
+        ShadowLockPatternUtils.setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
+        assertThat(mInitialLockSetupService.onBind(new Intent())).isNull();
+    }
+
+    @Test
+    public void testBindReturnsInstanceOfServiceInterface_ifLockNotSet() throws RemoteException {
+        assertThat(mInitialLockSetupService.onBind(
+                new Intent()) instanceof IInitialLockSetupService.Stub).isTrue();
+    }
+
+    @Test
+    public void testGetLockConfig_returnsCorrectConfig() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        LockConfig pinConfig = service.getLockConfig(LockTypes.PIN);
+        assertThat(pinConfig.enabled).isTrue();
+        assertThat(pinConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
+        LockConfig patternConfig = service.getLockConfig(LockTypes.PATTERN);
+        assertThat(patternConfig.enabled).isTrue();
+        assertThat(patternConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PATTERN_SIZE);
+        LockConfig passwordConfig = service.getLockConfig(LockTypes.PASSWORD);
+        assertThat(passwordConfig.enabled).isTrue();
+        assertThat(passwordConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
+    }
+
+    @Test
+    public void testCheckValidLock_tooShort() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        int result = service.checkValidLock(LockTypes.PASSWORD, "hi".getBytes());
+        assertThat(result & ValidateLockFlags.INVALID_LENGTH)
+                .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
+    }
+
+    @Test
+    public void testCheckValidLock_longEnough() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        int result = service.checkValidLock(LockTypes.PASSWORD, "password".getBytes());
+        assertThat(result & ValidateLockFlags.INVALID_LENGTH)
+                .isNotEqualTo(ValidateLockFlags.INVALID_LENGTH);
+    }
+
+    @Test
+    public void testCheckValidLockPin_withLetters() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        int result = service.checkValidLock(LockTypes.PIN, "12a3".getBytes());
+        assertThat(result & ValidateLockFlags.INVALID_BAD_SYMBOLS)
+                .isEqualTo(ValidateLockFlags.INVALID_BAD_SYMBOLS);
+    }
+
+    @Test
+    public void testCheckValidLockPattern_tooShort() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        byte[] pattern = new byte[LockPatternUtils.MIN_LOCK_PATTERN_SIZE - 1];
+        for (int i = 0; i < pattern.length; i++) {
+            pattern[i] = (byte) i;
+        }
+        int result = service.checkValidLock(LockTypes.PATTERN, pattern);
+        assertThat(result & ValidateLockFlags.INVALID_LENGTH)
+                .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
+    }
+
+    @Test
+    public void testCheckValidLockPattern_longEnough() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        byte[] pattern = new byte[LockPatternUtils.MIN_LOCK_PATTERN_SIZE + 1];
+        for (int i = 0; i < pattern.length; i++) {
+            pattern[i] = (byte) i;
+        }
+        int result = service.checkValidLock(LockTypes.PATTERN, pattern);
+        assertThat(result & ValidateLockFlags.INVALID_LENGTH)
+                .isNotEqualTo(ValidateLockFlags.INVALID_LENGTH);
+    }
+
+    @Test
+    public void testSetLockPassword_doesNotWorkWithExistingPassword() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        ShadowLockPatternUtils.setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
+        int result = service.setLock(LockTypes.PASSWORD, "password".getBytes());
+        assertThat(result).isEqualTo(SetLockCodes.FAIL_LOCK_EXISTS);
+    }
+
+    @Test
+    public void testSetLockPassword_doesNotWorkWithInvalidPassword() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        int result = service.setLock(LockTypes.PASSWORD, "hi".getBytes());
+        assertThat(result).isEqualTo(SetLockCodes.FAIL_LOCK_INVALID);
+    }
+
+    @Test
+    public void testSetLockPassword_setsDevicePassword() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        byte[] password = "password".getBytes();
+        // Need copy since password is cleared.
+        byte[] expectedPassword = Arrays.copyOf(password, password.length);
+        int result = service.setLock(LockTypes.PASSWORD, password);
+        assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
+        assertThat(Arrays.equals(ShadowLockPatternUtils.getSavedPassword(),
+                expectedPassword)).isTrue();
+    }
+
+    @Test
+    public void testSetLockPin_setsDevicePin() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        byte[] password = "1580".getBytes();
+        byte[] expectedPassword = Arrays.copyOf(password, password.length);
+        int result = service.setLock(LockTypes.PIN, password);
+        assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
+        assertThat(Arrays.equals(ShadowLockPatternUtils.getSavedPassword(),
+                expectedPassword)).isTrue();
+    }
+
+    @Test
+    public void testSetLockPattern_setsDevicePattern() throws RemoteException {
+        IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+                mInitialLockSetupService.onBind(new Intent()));
+        List<LockPatternView.Cell> pattern = new ArrayList<>();
+        pattern.add(LockPatternView.Cell.of(0, 0));
+        pattern.add(LockPatternView.Cell.of(1, 0));
+        pattern.add(LockPatternView.Cell.of(2, 0));
+        pattern.add(LockPatternView.Cell.of(0, 1));
+        byte[] patternBytes = new byte[pattern.size()];
+        for (int i = 0; i < patternBytes.length; i++) {
+            LockPatternView.Cell cell = pattern.get(i);
+            patternBytes[i] = InitialLockSetupHelper.getByteFromPatternCell(cell.getRow(),
+                    cell.getColumn());
+        }
+        int result = service.setLock(LockTypes.PATTERN, patternBytes);
+        assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
+        List<LockPatternView.Cell> savedPattern = ShadowLockPatternUtils.getSavedPattern();
+        assertThat(savedPattern).containsExactlyElementsIn(pattern);
+
+    }
+
+    @Test
+    public void testBindFails_ifNoPermissionGranted() {
+        ShadowContextWrapper shadowContextWrapper = Shadows.shadowOf((ContextWrapper) mContext);
+        shadowContextWrapper.denyPermissions(LOCK_PERMISSION);
+        assertThat(mInitialLockSetupService.onBind(new Intent())).isNull();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/car/settings/security/LockTypeBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/security/LockTypeBasePreferenceControllerTest.java
new file mode 100644
index 0000000..8272345
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/security/LockTypeBasePreferenceControllerTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class LockTypeBasePreferenceControllerTest {
+
+    // Test classes used to test LockTypeBasePreferenceController.
+    private static class TestFragment extends Fragment {
+    }
+
+    private static class TestLockPreferenceController extends LockTypeBasePreferenceController {
+
+        TestLockPreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected Fragment fragmentToOpen() {
+            return new TestFragment();
+        }
+
+        @Override
+        protected int[] allowedPasswordQualities() {
+            return new int[]{MATCHING_PASSWORD_QUALITY};
+        }
+    }
+
+    private static final int MATCHING_PASSWORD_QUALITY =
+            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+    private static final int NON_MATCHING_PASSWORD_QUALITY =
+            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<TestLockPreferenceController>
+            mPreferenceControllerHelper;
+    private TestLockPreferenceController mController;
+    private Preference mPreference;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestLockPreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_returnsTrue() {
+        assertThat(mController.handlePreferenceClicked(mPreference)).isTrue();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_goesToNextFragment() {
+        mPreference.performClick();
+        verify(mPreferenceControllerHelper.getMockFragmentController()).launchFragment(
+                any(TestFragment.class));
+    }
+
+    @Test
+    public void testRefreshUi_isCurrentLock() {
+        mController.setCurrentPasswordQuality(MATCHING_PASSWORD_QUALITY);
+        mController.refreshUi();
+        assertThat(mPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.current_screen_lock));
+    }
+
+    @Test
+    public void testRefreshUi_isNotCurrentLock() {
+        mController.setCurrentPasswordQuality(NON_MATCHING_PASSWORD_QUALITY);
+        mController.refreshUi();
+        assertThat(mPreference.getSummary()).isNotEqualTo(
+                mContext.getString(R.string.current_screen_lock));
+    }
+
+    @Test
+    public void testGetAvailabilityStatus_guestUser() {
+        when(mCarUserManagerHelper.isCurrentProcessGuestUser()).thenReturn(true);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void testGetAvailabilityStatus_otherUser() {
+        when(mCarUserManagerHelper.isCurrentProcessGuestUser()).thenReturn(false);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/security/NoLockPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/security/NoLockPreferenceControllerTest.java
new file mode 100644
index 0000000..bf34e34
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/security/NoLockPreferenceControllerTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowLockPatternUtils;
+import com.android.internal.widget.LockPatternUtils;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowLockPatternUtils.class})
+public class NoLockPreferenceControllerTest {
+
+    private static final byte[] TEST_CURRENT_PASSWORD = "test_password".getBytes();
+    private static final int TEST_USER = 10;
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<NoLockPreferenceController> mPreferenceControllerHelper;
+    private NoLockPreferenceController mController;
+    private Preference mPreference;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        ShadowLockPatternUtils.setInstance(mLockPatternUtils);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                NoLockPreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowLockPatternUtils.reset();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_returnsTrue() {
+        assertThat(mController.handlePreferenceClicked(mPreference)).isTrue();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_goesToNextFragment() {
+        mPreference.performClick();
+        verify(mPreferenceControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmRemoveScreenLockDialog.class), anyString());
+    }
+
+    @Test
+    public void testConfirmRemoveScreenLockListener_removesLock() {
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER);
+        mController.setCurrentPassword(TEST_CURRENT_PASSWORD);
+        mController.mRemoveLockListener.onConfirmRemoveScreenLock();
+        verify(mLockPatternUtils).clearLock(TEST_CURRENT_PASSWORD, TEST_USER);
+    }
+
+    @Test
+    public void testConfirmRemoveScreenLockListener_goesBack() {
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER);
+        mController.setCurrentPassword(TEST_CURRENT_PASSWORD);
+        mController.mRemoveLockListener.onConfirmRemoveScreenLock();
+        verify(mPreferenceControllerHelper.getMockFragmentController()).goBack();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java b/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java
index 3a6c646..567a009 100644
--- a/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -31,12 +32,12 @@
 public class PasswordHelperTest {
 
     private PasswordHelper mPasswordHelper;
-    private PasswordHelper mPinPresenter;
+    private PasswordHelper mPinHelper;
 
     @Before
     public void initObjects() {
-        mPasswordHelper = new PasswordHelper(false);
-        mPinPresenter = new PasswordHelper(true);
+        mPasswordHelper = new PasswordHelper(/* isPin= */ false);
+        mPinHelper = new PasswordHelper(/* isPin= */ true);
     }
 
     /**
@@ -45,17 +46,17 @@
      */
     @Test
     public void testValidatePasswordTooShort() {
-        String password = "lov";
+        byte[] password = "lov".getBytes();
         assertThat(mPasswordHelper.validate(password))
                 .isEqualTo(PasswordHelper.TOO_SHORT);
     }
 
     /**
-     * A test to check validate works when alphanumeric passwor contains white space.
+     * A test to check validate works when alphanumeric password contains white space.
      */
     @Test
     public void testValidatePasswordWhiteSpace() {
-        String password = "pass wd";
+        byte[] password = "pass wd".getBytes();
         assertThat(mPasswordHelper.validate(password))
                 .isEqualTo(PasswordHelper.NO_ERROR);
     }
@@ -66,7 +67,7 @@
      */
     @Test
     public void testValidatePasswordNonAscii() {
-        String password = "1passwýd";
+        byte[] password = "1passwýd".getBytes();
         assertThat(mPasswordHelper.validate(password))
                 .isEqualTo(PasswordHelper.CONTAINS_INVALID_CHARACTERS);
     }
@@ -76,8 +77,8 @@
      */
     @Test
     public void testValidatePinContainingNonDigits() {
-        String password = "1a34";
-        assertThat(mPinPresenter.validate(password))
+        byte[] password = "1a34".getBytes();
+        assertThat(mPinHelper.validate(password))
                 .isEqualTo(PasswordHelper.CONTAINS_NON_DIGITS);
     }
 
@@ -86,8 +87,72 @@
      */
     @Test
     public void testValidatePinWithTooFewDigits() {
-        String password = "12";
-        assertThat(mPinPresenter.validate(password))
+        byte[] password = "12".getBytes();
+        assertThat(mPinHelper.validate(password))
                 .isEqualTo(PasswordHelper.TOO_SHORT);
     }
+
+    /**
+     * A test to check that validate will work as expected for setup wizard passwords that are
+     * too short.
+     */
+    @Test
+    public void testValidatePasswordTooShort_setupWizard() {
+        byte[] password = "lov".getBytes();
+        assertThat(mPasswordHelper.validateSetupWizard(password) & ValidateLockFlags.INVALID_LENGTH)
+                .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
+    }
+
+    /**
+     * A test to check that validate works when setup wizard alphanumeric passwords contain white
+     * space.
+     */
+    @Test
+    public void testValidatePasswordWhiteSpace_setupWizard() {
+        byte[] password = "pass wd".getBytes();
+        assertThat(mPasswordHelper.validateSetupWizard(password)).isEqualTo(0);
+    }
+
+    /**
+     * A test to check validate works as expected for setup wizard alphanumeric password
+     * that contains an invalid character.
+     */
+    @Test
+    public void testValidatePasswordNonAscii_setupWizard() {
+        byte[] password = "1passwýd".getBytes();
+        assertThat(mPasswordHelper.validateSetupWizard(password)
+                & ValidateLockFlags.INVALID_BAD_SYMBOLS)
+                .isEqualTo(ValidateLockFlags.INVALID_BAD_SYMBOLS);
+    }
+
+    /**
+     * A test to check validate works as expected for setup wizard pin that contains non digits.
+     */
+    @Test
+    public void testValidatePinContainingNonDigits_setupWizard() {
+        byte[] password = "1a34".getBytes();
+        assertThat(mPinHelper.validateSetupWizard(password)
+                & ValidateLockFlags.INVALID_BAD_SYMBOLS)
+                .isEqualTo(ValidateLockFlags.INVALID_BAD_SYMBOLS);
+    }
+
+    /**
+     * A test to check validate works as expected for setup wizard pin with too few digits.
+     */
+    @Test
+    public void testValidatePinWithTooFewDigits_setupWizard() {
+        byte[] password = "12".getBytes();
+        assertThat(mPinHelper.validateSetupWizard(password) & ValidateLockFlags.INVALID_LENGTH)
+                .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
+    }
+
+    /**
+     * A test to check that validate works as expected for a valid setup wizard pin.
+     */
+    @Test
+    public void testValidatePinWithValidPin_setupWizard() {
+        byte[] password = "1234".getBytes();
+        assertThat(mPinHelper.validateSetupWizard(password)).isEqualTo(0);
+    }
+
 }
diff --git a/tests/robotests/src/com/android/car/settings/security/SecurityEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/security/SecurityEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..e7509d7
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/security/SecurityEntryPreferenceControllerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link SecurityEntryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class SecurityEntryPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private SecurityEntryPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+
+        mController = new PreferenceControllerTestHelper<>(RuntimeEnvironment.application,
+                SecurityEntryPreferenceController.class).getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_guestUser_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessGuestUser()).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_nonGuestUser_available() {
+        when(mCarUserManagerHelper.isCurrentProcessGuestUser()).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/sound/RingtonePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/sound/RingtonePreferenceControllerTest.java
new file mode 100644
index 0000000..967361a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/sound/RingtonePreferenceControllerTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 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.car.settings.sound;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ActivityResultCallback;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowRingtone;
+import com.android.car.settings.testutils.ShadowRingtoneManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowRingtoneManager.class, ShadowRingtone.class})
+public class RingtonePreferenceControllerTest {
+
+    private static final int TEST_RINGTONE_TYPE = RingtoneManager.TYPE_RINGTONE;
+    private static final String TEST_PATH = "/test/path/uri";
+    private static final Uri TEST_URI = new Uri.Builder().appendPath(TEST_PATH).build();
+    private static final String TEST_TITLE = "Test Preference Title";
+    private static final String TEST_RINGTONE_TITLE = "Test Ringtone Title";
+
+    // These are copied from android.app.Activity. That class is not accessible from this test
+    // because there is another test Activity with the same package.
+    private static final int ACTIVITY_RESULT_OK = -1;
+    private static final int ACTIVITY_RESULT_CANCELLED = 0;
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<RingtonePreferenceController>
+            mPreferenceControllerHelper;
+    private RingtonePreferenceController mController;
+    private RingtonePreference mRingtonePreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mRingtonePreference = new RingtonePreference(mContext, null);
+        mRingtonePreference.setTitle(TEST_TITLE);
+        mRingtonePreference.setRingtoneType(TEST_RINGTONE_TYPE);
+        mRingtonePreference.setShowSilent(true);  // Default value when instantiated via xml.
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                RingtonePreferenceController.class, mRingtonePreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        // Set the Uri to be null at the beginning of each test.
+        ShadowRingtoneManager.setActualDefaultRingtoneUri(mContext, TEST_RINGTONE_TYPE,
+                /* ringtoneUri= */ null);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowRingtoneManager.reset();
+        ShadowRingtone.reset();
+    }
+
+    @Test
+    public void testRefreshUi_ringtoneTitleSet() {
+        ShadowRingtoneManager.setActualDefaultRingtoneUri(mContext, TEST_RINGTONE_TYPE, TEST_URI);
+        ShadowRingtone.setExpectedTitleForUri(TEST_URI, TEST_RINGTONE_TITLE);
+        mController.refreshUi();
+        assertThat(mRingtonePreference.getSummary()).isEqualTo(TEST_RINGTONE_TITLE);
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_listenerTriggered() {
+        mRingtonePreference.performClick();
+        verify(mPreferenceControllerHelper.getMockFragmentController()).startActivityForResult(
+                any(Intent.class), anyInt(), any(ActivityResultCallback.class));
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_captureIntent_checkDefaultUri() {
+        ShadowRingtoneManager.setActualDefaultRingtoneUri(mContext, TEST_RINGTONE_TYPE, TEST_URI);
+        mRingtonePreference.performClick();
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mPreferenceControllerHelper.getMockFragmentController()).startActivityForResult(
+                intent.capture(), anyInt(), any(ActivityResultCallback.class));
+        assertThat((Uri) intent.getValue().getParcelableExtra(
+                RingtoneManager.EXTRA_RINGTONE_EXISTING_URI)).isEqualTo(TEST_URI);
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_captureIntent_checkDialogTitle() {
+        mRingtonePreference.performClick();
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mPreferenceControllerHelper.getMockFragmentController()).startActivityForResult(
+                intent.capture(), anyInt(), any(ActivityResultCallback.class));
+        assertThat(
+                intent.getValue().getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE)).isEqualTo(
+                TEST_TITLE);
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_captureIntent_checkRingtoneType() {
+        mRingtonePreference.performClick();
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mPreferenceControllerHelper.getMockFragmentController()).startActivityForResult(
+                intent.capture(), anyInt(), any(ActivityResultCallback.class));
+        assertThat(
+                intent.getValue().getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1)).isEqualTo(
+                TEST_RINGTONE_TYPE);
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_captureIntent_checkShowSilent() {
+        mRingtonePreference.performClick();
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mPreferenceControllerHelper.getMockFragmentController()).startActivityForResult(
+                intent.capture(), anyInt(), any(ActivityResultCallback.class));
+        assertThat(intent.getValue().getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT,
+                false)).isTrue();
+    }
+
+    @Test
+    public void testProcessActivityResult_wrongResult_defaultRingtoneNotSet() {
+        mController.processActivityResult(RingtonePreferenceController.REQUEST_CODE,
+                ACTIVITY_RESULT_CANCELLED, new Intent());
+        assertThat(ShadowRingtoneManager.getActualDefaultRingtoneUri(mContext,
+                TEST_RINGTONE_TYPE)).isNull();
+    }
+
+    @Test
+    public void testProcessActivityResult_correctResult_nullIntent_defaultRingtoneNotSet() {
+        mController.processActivityResult(RingtonePreferenceController.REQUEST_CODE,
+                ACTIVITY_RESULT_OK, null);
+        assertThat(ShadowRingtoneManager.getActualDefaultRingtoneUri(mContext,
+                TEST_RINGTONE_TYPE)).isNull();
+    }
+
+    @Test
+    public void testProcessActivityResult_correctResult_validIntent_defaultRingtoneSet() {
+        Intent data = new Intent();
+        data.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, TEST_URI);
+        mController.processActivityResult(RingtonePreferenceController.REQUEST_CODE,
+                ACTIVITY_RESULT_OK, data);
+        assertThat(ShadowRingtoneManager.getActualDefaultRingtoneUri(mContext,
+                TEST_RINGTONE_TYPE)).isEqualTo(TEST_URI);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java
new file mode 100644
index 0000000..c5d83fd
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2018 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.car.settings.sound;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.media.CarAudioManager;
+import android.content.Context;
+import android.media.Ringtone;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.SeekBarPreference;
+import com.android.car.settings.testutils.ShadowCar;
+import com.android.car.settings.testutils.ShadowRingtoneManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowRingtoneManager.class})
+public class VolumeSettingsPreferenceControllerTest {
+
+    private static final int GROUP_ID = 0;
+    private static final int TEST_MIN_VOLUME = 0;
+    private static final int TEST_VOLUME = 40;
+    private static final int TEST_NEW_VOLUME = 80;
+    private static final int TEST_MAX_VOLUME = 100;
+
+    private PreferenceControllerTestHelper<TestVolumeSettingsPreferenceController>
+            mPreferenceControllerHelper;
+    private TestVolumeSettingsPreferenceController mController;
+    private PreferenceGroup mPreferenceGroup;
+    @Mock
+    private CarAudioManager mCarAudioManager;
+    @Mock
+    private Ringtone mRingtone;
+
+    /** Extend class to provide test resource which doesn't require internal android resources. */
+    public static class TestVolumeSettingsPreferenceController extends
+            VolumeSettingsPreferenceController {
+
+        public TestVolumeSettingsPreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        public int carVolumeItemsXml() {
+            return R.xml.test_car_volume_items;
+        }
+    }
+
+    @Before
+    public void setUp() throws CarNotConnectedException {
+        MockitoAnnotations.initMocks(this);
+        ShadowCar.setCarManager(Car.AUDIO_SERVICE, mCarAudioManager);
+        ShadowRingtoneManager.setRingtone(mRingtone);
+
+        Context context = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
+                TestVolumeSettingsPreferenceController.class, mPreferenceGroup);
+        mController = mPreferenceControllerHelper.getController();
+
+        when(mCarAudioManager.getVolumeGroupCount()).thenReturn(1);
+        when(mCarAudioManager.getUsagesForVolumeGroupId(GROUP_ID)).thenReturn(new int[]{1, 2});
+        when(mCarAudioManager.getGroupMinVolume(GROUP_ID)).thenReturn(TEST_MIN_VOLUME);
+        when(mCarAudioManager.getGroupVolume(GROUP_ID)).thenReturn(TEST_VOLUME);
+        when(mCarAudioManager.getGroupMaxVolume(GROUP_ID)).thenReturn(TEST_MAX_VOLUME);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCar.reset();
+        ShadowRingtoneManager.reset();
+    }
+
+    @Test
+    public void testRefreshUi_serviceNotStarted() {
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testRefreshUi_serviceStarted() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testRefreshUi_serviceStarted_multipleCalls() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+
+        // Calling this multiple times shouldn't increase the number of elements.
+        mController.refreshUi();
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testRefreshUi_createdPreferenceHasMinMax() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
+        assertThat(preference.getMin()).isEqualTo(TEST_MIN_VOLUME);
+        assertThat(preference.getValue()).isEqualTo(TEST_VOLUME);
+        assertThat(preference.getMax()).isEqualTo(TEST_MAX_VOLUME);
+    }
+
+    @Test
+    public void testOnPreferenceChange_ringtonePlays() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
+        preference.getOnPreferenceChangeListener().onPreferenceChange(preference, TEST_NEW_VOLUME);
+        verify(mRingtone).play();
+    }
+
+    @Test
+    public void testOnPreferenceChange_audioManagerSet() throws CarNotConnectedException {
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        mController.refreshUi();
+        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
+        preference.getOnPreferenceChangeListener().onPreferenceChange(preference, TEST_NEW_VOLUME);
+        verify(mCarAudioManager).setGroupVolume(GROUP_ID, TEST_NEW_VOLUME, 0);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsRingtoneManagerTest.java b/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsRingtoneManagerTest.java
new file mode 100644
index 0000000..b6e3f85
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsRingtoneManagerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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.car.settings.sound;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.media.Ringtone;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowRingtoneManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowRingtoneManager.class})
+public class VolumeSettingsRingtoneManagerTest {
+
+    private static final int TEST_GROUP_ID = 1;
+    private static final int TEST_USAGE_ID = 18;
+
+    private Context mContext;
+    private VolumeSettingsRingtoneManager mRingtoneManager;
+    @Mock
+    private Ringtone mRingtone;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowRingtoneManager.setRingtone(mRingtone);
+        mContext = RuntimeEnvironment.application;
+        mRingtoneManager = new VolumeSettingsRingtoneManager(mContext);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowRingtoneManager.reset();
+        when(mRingtone.isPlaying()).thenReturn(false);
+    }
+
+    @Test
+    public void testPlayAudioFeedback_play_playUntilTimeout() {
+        mRingtoneManager.playAudioFeedback(TEST_GROUP_ID, TEST_USAGE_ID);
+        verify(mRingtone).play();
+        when(mRingtone.isPlaying()).thenReturn(true);
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+        verify(mRingtone).stop();
+    }
+
+    @Test
+    public void testPlayAudioFeedback_play_stoppedBeforeTimeout() {
+        mRingtoneManager.playAudioFeedback(TEST_GROUP_ID, TEST_USAGE_ID);
+        verify(mRingtone).play();
+        when(mRingtone.isPlaying()).thenReturn(false);
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+        verify(mRingtone, never()).stop();
+    }
+
+    @Test
+    public void testStopCurrentRingtone_stop() {
+        mRingtoneManager.playAudioFeedback(TEST_GROUP_ID, TEST_USAGE_ID);
+        mRingtoneManager.stopCurrentRingtone();
+        verify(mRingtone).stop();
+    }
+
+    @Test
+    public void testStopCurrentRingtone_noCurrentRingtone() {
+        mRingtoneManager.stopCurrentRingtone();
+        verify(mRingtone, never()).stop();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragmentTest.java b/tests/robotests/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragmentTest.java
new file mode 100644
index 0000000..ad0dc19
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragmentTest.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.android.car.settings.storage.AppStorageSettingsDetailsFragment.EXTRA_PACKAGE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.usage.StorageStats;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowActivityManager;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.car.settings.testutils.ShadowApplicationsState;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowRestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.StorageStatsSource;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link AppStorageSettingsDetailsFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationsState.class, ShadowCarUserManagerHelper.class,
+        ShadowRestrictedLockUtils.class, ShadowApplicationPackageManager.class,
+        ShadowActivityManager.class})
+public class AppStorageSettingsDetailsFragmentTest {
+
+    private static final String PACKAGE_NAME = "com.google.packageName";
+    private static final String SOURCE = "source";
+    private static final int UID = 12;
+    private static final String LABEL = "label";
+    private static final String SIZE_STR = "12.34 MB";
+    private static final int TEST_USER_ID = 10;
+
+    private Context mContext;
+    private AppStorageSettingsDetailsFragment mFragment;
+    private FragmentController<AppStorageSettingsDetailsFragment> mFragmentController;
+
+    @Mock
+    private ApplicationsState mApplicationsState;
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Mock
+    private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
+
+    @Mock
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mFragment = new AppStorageSettingsDetailsFragment();
+        Bundle bundle = new Bundle();
+        bundle.putString(EXTRA_PACKAGE_NAME, PACKAGE_NAME);
+        mFragment.setArguments(bundle);
+        mFragmentController = FragmentController.of(mFragment);
+
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.uid = UID;
+        appInfo.sourceDir = SOURCE;
+
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo,
+                1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        appEntry.info.packageName = PACKAGE_NAME;
+        when(mApplicationsState.getEntry(eq(PACKAGE_NAME), anyInt())).thenReturn(appEntry);
+        // Set user.
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER_ID);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        ShadowApplicationsState.setInstance(mApplicationsState);
+        mFragmentController.create();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationsState.reset();
+        ShadowCarUserManagerHelper.reset();
+        ShadowRestrictedLockUtils.reset();
+        ShadowApplicationPackageManager.reset();
+        ShadowActivityManager.reset();
+    }
+
+    @Test
+    public void onActivityCreated_defaultStatus_shouldShowCacheButtons() {
+        mFragment.onActivityCreated(null);
+
+        assertThat(findClearCacheButton(mFragment.requireActivity())).isNotNull();
+        assertThat(findClearCacheButton(mFragment.requireActivity()).getVisibility()).isEqualTo(
+                View.VISIBLE);
+        assertThat(findClearCacheButton(mFragment.requireActivity()).getText()).isEqualTo(
+                mContext.getString(R.string.storage_clear_cache_btn_text));
+
+        assertThat(findClearStorageButton(mFragment.requireActivity())).isNotNull();
+        assertThat(findClearStorageButton(mFragment.requireActivity()).getVisibility()).isEqualTo(
+                View.VISIBLE);
+        assertThat(findClearStorageButton(mFragment.requireActivity()).getText()).isEqualTo(
+                mContext.getString(R.string.storage_clear_user_data_text));
+    }
+
+    @Test
+    public void onActivityCreated_defaultStatus_shouldShowClearStorageButtons() {
+        mFragment.onActivityCreated(null);
+
+        assertThat(findClearStorageButton(mFragment.requireActivity())).isNotNull();
+        assertThat(findClearStorageButton(mFragment.requireActivity()).getVisibility()).isEqualTo(
+                View.VISIBLE);
+        assertThat(findClearStorageButton(mFragment.requireActivity()).getText()).isEqualTo(
+                mContext.getString(R.string.storage_clear_user_data_text));
+    }
+
+    @Test
+    public void handleClearCacheClick_disallowedBySystem_shouldNotDeleteApplicationCache() {
+        ShadowRestrictedLockUtils.setEnforcedAdmin(mEnforcedAdmin);
+        ShadowApplicationPackageManager.setPackageManager(mPackageManager);
+
+        doNothing().when(mPackageManager).deleteApplicationCacheFiles(anyString(), any());
+
+        mFragmentController.resume();
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = 100;
+        stats.dataBytes = 50;
+        stats.cacheBytes = 0;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mFragment.onActivityCreated(null);
+        mFragment.onDataLoaded(storageStats, false, false);
+        findClearCacheButton(mFragment.requireActivity()).performClick();
+
+        verify(mPackageManager, never()).deleteApplicationCacheFiles(anyString(), any());
+    }
+
+    @Test
+    public void handleClearCacheClick_allowedBySystem_shouldNotDeleteApplicationCache() {
+        ShadowRestrictedLockUtils.setEnforcedAdmin(mEnforcedAdmin);
+        ShadowRestrictedLockUtils.setHasBaseUserRestriction(true);
+        ShadowApplicationPackageManager.setPackageManager(mPackageManager);
+
+        doNothing().when(mPackageManager).deleteApplicationCacheFiles(anyString(), any());
+
+        mFragmentController.resume();
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = 100;
+        stats.dataBytes = 50;
+        stats.cacheBytes = 10;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mFragment.onActivityCreated(null);
+        mFragment.onDataLoaded(storageStats, false, false);
+        findClearCacheButton(mFragment.requireActivity()).performClick();
+
+        verify(mPackageManager).deleteApplicationCacheFiles(anyString(), any());
+    }
+
+    @Test
+    public void handleClearDataClick_disallowedBySystem_shouldNotShowDialogToClear() {
+        ShadowRestrictedLockUtils.setEnforcedAdmin(mEnforcedAdmin);
+        ShadowApplicationPackageManager.setPackageManager(mPackageManager);
+
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = 100;
+        stats.dataBytes = 10;
+        stats.cacheBytes = 10;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mFragment.onActivityCreated(null);
+        mFragment.onDataLoaded(storageStats, false, false);
+        findClearStorageButton(mFragment.requireActivity()).performClick();
+
+        assertThat(mFragment.getFragmentManager().findFragmentByTag(
+                AppStorageSettingsDetailsFragment.CONFIRM_CLEAR_STORAGE_DIALOG_TAG)).isNull();
+    }
+
+    @Test
+    public void handleClearDataClick_allowedBySystem_shouldShowDialogToClear() {
+        ShadowRestrictedLockUtils.setEnforcedAdmin(mEnforcedAdmin);
+        ShadowRestrictedLockUtils.setHasBaseUserRestriction(true);
+        ShadowApplicationPackageManager.setPackageManager(mPackageManager);
+
+        mFragmentController.resume();
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = 100;
+        stats.dataBytes = 50;
+        stats.cacheBytes = 10;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mFragment.onActivityCreated(null);
+        mFragment.onDataLoaded(storageStats, false, false);
+        findClearStorageButton(mFragment.requireActivity()).performClick();
+
+        assertThat(mFragment.getFragmentManager()).isNotNull();
+    }
+
+    @Test
+    public void onDataLoaded_noResult_buttonsShouldBeDisabled() {
+        mFragment.onActivityCreated(null);
+
+        mFragment.onDataLoaded(null, false, false);
+
+        assertThat(findClearCacheButton(mFragment.requireActivity()).isEnabled()).isFalse();
+        assertThat(findClearStorageButton(mFragment.requireActivity()).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onDataLoaded_resultLoaded_cacheButtonsShouldBeEnabled() {
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = 100;
+        stats.dataBytes = 50;
+        stats.cacheBytes = 10;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mFragment.onActivityCreated(null);
+        mFragment.onDataLoaded(storageStats, false, false);
+
+        assertThat(findClearCacheButton(mFragment.requireActivity()).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onDataLoaded_resultLoaded_dataButtonsShouldBeEnabled() {
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = 100;
+        stats.dataBytes = 50;
+        stats.cacheBytes = 10;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mFragment.onActivityCreated(null);
+        mFragment.onDataLoaded(storageStats, false, false);
+
+        assertThat(findClearStorageButton(mFragment.requireActivity()).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateUiWithSize_resultLoaded_cacheButtonDisabledAndDataButtonsEnabled() {
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = 100;
+        stats.dataBytes = 50;
+        stats.cacheBytes = 0;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mFragment.onActivityCreated(null);
+        mFragment.onDataLoaded(storageStats, false, false);
+
+        assertThat(findClearCacheButton(mFragment.requireActivity()).isEnabled()).isFalse();
+        assertThat(findClearStorageButton(mFragment.requireActivity()).isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onDataLoaded_resultLoaded_cacheButtonEnabledAndDataButtonDisabled() {
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = 100;
+        stats.dataBytes = 10;
+        stats.cacheBytes = 10;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mFragment.onActivityCreated(null);
+        mFragment.onDataLoaded(storageStats, false, false);
+
+        assertThat(findClearCacheButton(mFragment.requireActivity()).isEnabled()).isTrue();
+        assertThat(findClearStorageButton(mFragment.requireActivity()).isEnabled()).isFalse();
+    }
+
+    private Button findClearCacheButton(Activity activity) {
+        return activity.findViewById(R.id.action_button2);
+    }
+
+    private Button findClearStorageButton(Activity activity) {
+        return activity.findViewById(R.id.action_button1);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/AppsStorageStatsManagerTest.java b/tests/robotests/src/com/android/car/settings/storage/AppsStorageStatsManagerTest.java
new file mode 100644
index 0000000..b6da6c6
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/AppsStorageStatsManagerTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.usage.StorageStats;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import androidx.loader.app.LoaderManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.settingslib.applications.StorageStatsSource;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link AppsStorageStatsManager}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AppsStorageStatsManagerTest {
+
+    private static final int USER_ID = 10;
+
+    private Context mContext;
+    private AppsStorageStatsManager mAppsStorageStatsManager;
+
+    @Captor
+    private ArgumentCaptor<LoaderManager.LoaderCallbacks<StorageStatsSource.AppStorageStats>>
+            mCallbacksArgumentCaptor;
+
+    @Mock
+    private AppsStorageStatsManager.Callback mCallback1;
+
+    @Mock
+    private AppsStorageStatsManager.Callback mCallback2;
+
+    @Mock
+    private LoaderManager mLoaderManager;
+
+    @Mock
+    private ApplicationInfo mApplicationInfo;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mAppsStorageStatsManager = new AppsStorageStatsManager(mContext);
+        mAppsStorageStatsManager.startLoading(mLoaderManager, mApplicationInfo, USER_ID, false,
+                false);
+        verify(mLoaderManager).restartLoader(eq(1), eq(Bundle.EMPTY),
+                mCallbacksArgumentCaptor.capture());
+    }
+
+    @Test
+    public void callback_onLoadFinished_listenerOnDataLoadedCalled() throws Exception {
+        mAppsStorageStatsManager.registerListener(mCallback1);
+        mAppsStorageStatsManager.registerListener(mCallback2);
+
+        StorageStats stats = new StorageStats();
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mCallbacksArgumentCaptor.getValue().onLoadFinished(null, storageStats);
+
+        verify(mCallback1).onDataLoaded(storageStats, false, false);
+        verify(mCallback2).onDataLoaded(storageStats, false, false);
+    }
+
+    @Test
+    public void callback_unregisterListener_onlyOneListenerOnDataLoadedCalled() throws Exception {
+        mAppsStorageStatsManager.registerListener(mCallback1);
+        mAppsStorageStatsManager.registerListener(mCallback2);
+        mAppsStorageStatsManager.unregisterListener(mCallback2);
+        StorageStats stats = new StorageStats();
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        mCallbacksArgumentCaptor.getValue().onLoadFinished(null, storageStats);
+
+        verify(mCallback1).onDataLoaded(storageStats, false, false);
+        verify(mCallback2, never()).onDataLoaded(storageStats, false, false);
+    }
+
+    @Test
+    public void callback_notLoaded_listenerOnDataLoadedCalled() throws Exception {
+        mAppsStorageStatsManager.registerListener(mCallback1);
+        mAppsStorageStatsManager.registerListener(mCallback2);
+
+        StorageStats stats = new StorageStats();
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        verify(mCallback1, never()).onDataLoaded(storageStats, false, false);
+        verify(mCallback2, never()).onDataLoaded(storageStats, false, false);
+    }
+
+    @Test
+    public void callback_cachedCleared_listenerOnDataLoadedCalled() throws Exception {
+        mAppsStorageStatsManager = new AppsStorageStatsManager(mContext);
+        mAppsStorageStatsManager.startLoading(mLoaderManager, mApplicationInfo, USER_ID, true,
+                false);
+
+        mAppsStorageStatsManager.registerListener(mCallback1);
+        mAppsStorageStatsManager.registerListener(mCallback2);
+
+        StorageStats stats = new StorageStats();
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        verify(mCallback1, never()).onDataLoaded(storageStats, true, false);
+        verify(mCallback2, never()).onDataLoaded(storageStats, true, false);
+    }
+
+    @Test
+    public void callback_userDataCleared_listenerOnDataLoadedCalled() throws Exception {
+        mAppsStorageStatsManager = new AppsStorageStatsManager(mContext);
+        mAppsStorageStatsManager.startLoading(mLoaderManager, mApplicationInfo, USER_ID, false,
+                true);
+
+        mAppsStorageStatsManager.registerListener(mCallback1);
+        mAppsStorageStatsManager.registerListener(mCallback2);
+
+        StorageStats stats = new StorageStats();
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        verify(mCallback1, never()).onDataLoaded(storageStats, false, true);
+        verify(mCallback2, never()).onDataLoaded(storageStats, false, true);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/FileSizeFormatterTest.java b/tests/robotests/src/com/android/car/settings/storage/FileSizeFormatterTest.java
new file mode 100644
index 0000000..8b64456
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/FileSizeFormatterTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.android.car.settings.storage.FileSizeFormatter.GIGABYTE_IN_BYTES;
+import static com.android.car.settings.storage.FileSizeFormatter.MEGABYTE_IN_BYTES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+
+/** Unit test for {@link FileSizeFormatter}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class FileSizeFormatterTest {
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @Test
+    public void formatFileSize_zero() throws Exception {
+        assertThat(
+                FileSizeFormatter.formatFileSize(
+                        mContext,
+                        0 /* size */,
+                        com.android.internal.R.string.gigabyteShort,
+                        GIGABYTE_IN_BYTES))
+                .isEqualTo("0.00 GB");
+    }
+
+    @Test
+    public void formatFileSize_smallSize() throws Exception {
+        assertThat(
+                FileSizeFormatter.formatFileSize(
+                        mContext,
+                        MEGABYTE_IN_BYTES * 11 /* size */,
+                        com.android.internal.R.string.gigabyteShort,
+                        GIGABYTE_IN_BYTES))
+                .isEqualTo("0.01 GB");
+    }
+
+    @Test
+    public void formatFileSize_lessThanOneSize() throws Exception {
+        assertThat(
+                FileSizeFormatter.formatFileSize(
+                        mContext,
+                        MEGABYTE_IN_BYTES * 155 /* size */,
+                        com.android.internal.R.string.gigabyteShort,
+                        GIGABYTE_IN_BYTES))
+                .isEqualTo("0.16 GB");
+    }
+
+    @Test
+    public void formatFileSize_greaterThanOneSize() throws Exception {
+        assertThat(
+                FileSizeFormatter.formatFileSize(
+                        mContext,
+                        MEGABYTE_IN_BYTES * 1551 /* size */,
+                        com.android.internal.R.string.gigabyteShort,
+                        GIGABYTE_IN_BYTES))
+                .isEqualTo("1.6 GB");
+    }
+
+    @Test
+    public void formatFileSize_greaterThanTen() throws Exception {
+        // Should round down due to truncation
+        assertThat(
+                FileSizeFormatter.formatFileSize(
+                        mContext,
+                        GIGABYTE_IN_BYTES * 15 + MEGABYTE_IN_BYTES * 50 /* size */,
+                        com.android.internal.R.string.gigabyteShort,
+                        GIGABYTE_IN_BYTES))
+                .isEqualTo("15 GB");
+    }
+
+    @Test
+    public void formatFileSize_handlesNegativeFileSizes() throws Exception {
+        assertThat(
+                FileSizeFormatter.formatFileSize(
+                        mContext,
+                        MEGABYTE_IN_BYTES * -155 /* size */,
+                        com.android.internal.R.string.gigabyteShort,
+                        GIGABYTE_IN_BYTES))
+                .isEqualTo("-0.16 GB");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageApplicationListPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageApplicationListPreferenceControllerTest.java
new file mode 100644
index 0000000..04cd87d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageApplicationListPreferenceControllerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+/** Unit test for {@link StorageApplicationListPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class StorageApplicationListPreferenceControllerTest {
+
+    private static final String SOURCE = "source";
+    private static final int UID = 12;
+    private static final String LABEL = "label";
+    private static final String SIZE_STR = "12.34 MB";
+    private static final String UPDATED_SIZE_STR = "15.34 MB";
+    private static final String PACKAGE_NAME = "com.google.packageName";
+
+    private Context mContext;
+    private LogicalPreferenceGroup mLogicalPreferenceGroup;
+    private StorageApplicationListPreferenceController mController;
+    private PreferenceControllerTestHelper<StorageApplicationListPreferenceController>
+            mPreferenceControllerHelper;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mLogicalPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                StorageApplicationListPreferenceController.class, mLogicalPreferenceGroup);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void defaultInitialize_hasNoPreference() {
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onDataLoaded_addPreference_hasOnePreference() {
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.uid = UID;
+        appInfo.sourceDir = SOURCE;
+
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo,
+                1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        appEntry.info.packageName = PACKAGE_NAME;
+        apps.add(appEntry);
+
+        mController.onDataLoaded(apps);
+
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getTitle()).isEqualTo(LABEL);
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getSummary()).isEqualTo(SIZE_STR);
+    }
+
+    @Test
+    public void onDataLoaded_updatePreference_hasOnePreferenceWithUpdatedValues() {
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.uid = UID;
+        appInfo.sourceDir = SOURCE;
+
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo,
+                1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        appEntry.info.packageName = PACKAGE_NAME;
+        apps.add(appEntry);
+
+        mController.onDataLoaded(apps);
+
+        apps.clear();
+        appEntry = new ApplicationsState.AppEntry(mContext, appInfo, 1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = UPDATED_SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        appEntry.info.packageName = PACKAGE_NAME;
+        apps.add(appEntry);
+
+        mController.onDataLoaded(apps);
+
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getTitle()).isEqualTo(LABEL);
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getSummary()).isEqualTo(
+                UPDATED_SIZE_STR);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageAsyncLoaderTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageAsyncLoaderTest.java
new file mode 100644
index 0000000..a02e44a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageAsyncLoaderTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.usage.StorageStats;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.net.TrafficStats;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowApplicationPackageManager;
+import com.android.settingslib.applications.StorageStatsSource;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit test for {@link StorageAsyncLoader}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class})
+public class StorageAsyncLoaderTest {
+    private static final int PRIMARY_USER_ID = 0;
+    private static final int SECONDARY_USER_ID = 10;
+    private static final String PACKAGE_NAME_1 = "com.blah.test";
+    private static final String PACKAGE_NAME_2 = "com.blah.test2";
+    private static final String DEFAULT_PACKAGE_NAME = "com.android.car.settings";
+    private static final long DEFAULT_QUOTA = 64 * TrafficStats.MB_IN_BYTES;
+
+    @Mock
+    private StorageStatsSource mSource;
+
+    private Context mContext;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private List<ApplicationInfo> mInfo = new ArrayList<>();
+    private List<UserInfo> mUsers;
+
+    private StorageAsyncLoader mLoader;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mInfo = new ArrayList<>();
+        mLoader = new StorageAsyncLoader(mContext, mCarUserManagerHelper, mSource);
+        UserInfo info = new UserInfo();
+        mUsers = new ArrayList<>();
+        mUsers.add(info);
+        when(mCarUserManagerHelper.getAllUsers()).thenReturn(mUsers);
+        when(mSource.getCacheQuotaBytes(any(), anyInt())).thenReturn(DEFAULT_QUOTA);
+        // there is always a "com.android.car.settings" package added by default with category
+        // otherAppsSize lets remove it first for testing.
+        getShadowApplicationManager().removePackage(DEFAULT_PACKAGE_NAME);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationPackageManager.reset();
+    }
+
+    @Test
+    public void testLoadingApps() throws Exception {
+        addPackage(PACKAGE_NAME_1, 0, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED);
+        addPackage(PACKAGE_NAME_2, 0, 100, 1000, ApplicationInfo.CATEGORY_UNDEFINED);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(PRIMARY_USER_ID).getGamesSize()).isEqualTo(0L);
+        assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(2200L);
+    }
+
+    @Test
+    public void testGamesAreFiltered() throws Exception {
+        addPackage(PACKAGE_NAME_1, 0, 1, 10, ApplicationInfo.CATEGORY_GAME);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(PRIMARY_USER_ID).getGamesSize()).isEqualTo(11L);
+        assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(0L);
+    }
+
+    @Test
+    public void testLegacyGamesAreFiltered() throws Exception {
+        ApplicationInfo info =
+                addPackage(PACKAGE_NAME_1, 0, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED);
+        info.flags = ApplicationInfo.FLAG_IS_GAME;
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(PRIMARY_USER_ID).getGamesSize()).isEqualTo(11L);
+        assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(0L);
+    }
+
+    @Test
+    public void testCacheIsNotIgnored() throws Exception {
+        addPackage(PACKAGE_NAME_1, 100, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(111L);
+    }
+
+    @Test
+    public void testMultipleUsers() throws Exception {
+        UserInfo info = new UserInfo();
+        info.id = SECONDARY_USER_ID;
+        mUsers.add(info);
+        when(mSource.getExternalStorageStats(any(), eq(UserHandle.SYSTEM)))
+                .thenReturn(new StorageStatsSource.ExternalStorageStats(9, 2, 3, 4, 0));
+        when(mSource.getExternalStorageStats(any(), eq(new UserHandle(SECONDARY_USER_ID))))
+                .thenReturn(new StorageStatsSource.ExternalStorageStats(10, 3, 3, 4, 0));
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(2);
+        assertThat(result.get(PRIMARY_USER_ID).getExternalStats().totalBytes).isEqualTo(9L);
+        assertThat(result.get(SECONDARY_USER_ID).getExternalStats().totalBytes).isEqualTo(10L);
+    }
+
+    @Test
+    public void testUpdatedSystemAppCodeSizeIsCounted() throws Exception {
+        ApplicationInfo systemApp =
+                addPackage(PACKAGE_NAME_1, 100, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED);
+        systemApp.flags = ApplicationInfo.FLAG_SYSTEM & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(111L);
+    }
+
+    @Test
+    public void testVideoAppsAreFiltered() throws Exception {
+        addPackage(PACKAGE_NAME_1, 0, 1, 10, ApplicationInfo.CATEGORY_VIDEO);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(PRIMARY_USER_ID).getVideoAppsSize()).isEqualTo(11L);
+        assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(0L);
+    }
+
+    @Test
+    public void testRemovedPackageDoesNotCrash() throws Exception {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = PACKAGE_NAME_1;
+        info.category = ApplicationInfo.CATEGORY_UNDEFINED;
+        mInfo.add(info);
+        when(mSource.getStatsForPackage(any(), anyString(), any(UserHandle.class)))
+                .thenThrow(new NameNotFoundException());
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        // Should not crash.
+    }
+
+    @Test
+    public void testPackageIsNotDoubleCounted() throws Exception {
+        UserInfo info = new UserInfo();
+        info.id = SECONDARY_USER_ID;
+        mUsers.add(info);
+        when(mSource.getExternalStorageStats(anyString(), eq(UserHandle.SYSTEM)))
+                .thenReturn(new StorageStatsSource.ExternalStorageStats(9, 2, 3, 4, 0));
+        when(mSource.getExternalStorageStats(anyString(), eq(new UserHandle(SECONDARY_USER_ID))))
+                .thenReturn(new StorageStatsSource.ExternalStorageStats(10, 3, 3, 4, 0));
+        addPackage(PACKAGE_NAME_1, 0, 1, 10, ApplicationInfo.CATEGORY_VIDEO);
+        ArrayList<ApplicationInfo> secondaryUserApps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = PACKAGE_NAME_1;
+        appInfo.category = ApplicationInfo.CATEGORY_VIDEO;
+        secondaryUserApps.add(appInfo);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(2);
+        assertThat(result.get(PRIMARY_USER_ID).getVideoAppsSize()).isEqualTo(11L);
+        // No code size for the second user.
+        assertThat(result.get(SECONDARY_USER_ID).getVideoAppsSize()).isEqualTo(10L);
+    }
+
+    @Test
+    public void testCacheOveragesAreCountedAsFree() throws Exception {
+        addPackage(PACKAGE_NAME_1, DEFAULT_QUOTA + 100, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(DEFAULT_QUOTA + 11);
+    }
+
+    private ApplicationInfo addPackage(String packageName, long cacheSize, long codeSize,
+            long dataSize, int category) throws Exception {
+        StorageStats stats = new StorageStats();
+        stats.codeBytes = codeSize;
+        stats.dataBytes = dataSize + cacheSize;
+        stats.cacheBytes = cacheSize;
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+
+        when(mSource.getStatsForPackage(any(), anyString(), any(UserHandle.class)))
+                .thenReturn(storageStats);
+
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = packageName;
+        info.category = category;
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.applicationInfo = info;
+        packageInfo.packageName = packageName;
+        getShadowApplicationManager().addPackage(packageInfo);
+        return info;
+    }
+
+    private ShadowApplicationPackageManager getShadowApplicationManager() {
+        return Shadow.extract(mContext.getPackageManager());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageFileCategoryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageFileCategoryPreferenceControllerTest.java
new file mode 100644
index 0000000..81e6048
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageFileCategoryPreferenceControllerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static android.os.storage.VolumeInfo.MOUNT_FLAG_PRIMARY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ProgressBarPreference;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowStorageManagerVolumeProvider;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link StorageFileCategoryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowStorageManagerVolumeProvider.class, ShadowCarUserManagerHelper.class})
+public class StorageFileCategoryPreferenceControllerTest {
+
+    private static final int TEST_USER = 10;
+
+    private Context mContext;
+    private ProgressBarPreference mProgressBarPreference;
+    private StorageFileCategoryPreferenceController mController;
+    private PreferenceControllerTestHelper<StorageFileCategoryPreferenceController>
+            mPreferenceControllerHelper;
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        mProgressBarPreference = new ProgressBarPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                StorageFileCategoryPreferenceController.class, mProgressBarPreference);
+        mController = mPreferenceControllerHelper.getController();
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(TEST_USER);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowStorageManagerVolumeProvider.reset();
+    }
+
+    @Test
+    public void handlePreferenceClicked_currentUser_startNewActivity() {
+        VolumeInfo volumeInfo = new VolumeInfo("id", VolumeInfo.TYPE_EMULATED, null, "id");
+        volumeInfo.mountFlags = MOUNT_FLAG_PRIMARY;
+        ShadowStorageManagerVolumeProvider.setVolumeInfo(volumeInfo);
+
+        mProgressBarPreference.performClick();
+        ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivityAsUser(argumentCaptor.capture(), nullable(UserHandle.class));
+
+        Intent intent = argumentCaptor.getValue();
+        Intent browseIntent = volumeInfo.buildBrowseIntent();
+        assertThat(intent.getAction()).isEqualTo(browseIntent.getAction());
+        assertThat(intent.getData()).isEqualTo(browseIntent.getData());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageMediaCategoryDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageMediaCategoryDetailPreferenceControllerTest.java
new file mode 100644
index 0000000..5b55242
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageMediaCategoryDetailPreferenceControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+/** Unit test for {@link StorageMediaCategoryDetailPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class StorageMediaCategoryDetailPreferenceControllerTest {
+
+    private static final String SOURCE = "source";
+    private static final int UID = 12;
+    private static final long EXTRA_AUDIO_BYTES = 100;
+    private static final String LABEL = "label";
+    private static final String SIZE_STR = "12.34 MB";
+    private static final String PACKAGE_NAME = "com.google.packageName";
+
+    private Context mContext;
+    private LogicalPreferenceGroup mLogicalPreferenceGroup;
+    private StorageMediaCategoryDetailPreferenceController mController;
+    private PreferenceControllerTestHelper<StorageMediaCategoryDetailPreferenceController>
+            mPreferenceControllerHelper;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mLogicalPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                StorageMediaCategoryDetailPreferenceController.class, mLogicalPreferenceGroup);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void refreshUi_defaultInitialize_hasNoPreference() {
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onDataLoaded_addPreference_hasTwoPreferences() {
+        ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.uid = UID;
+        appInfo.sourceDir = SOURCE;
+
+        ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo,
+                1234L);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SIZE_STR;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+        appEntry.info.packageName = PACKAGE_NAME;
+        apps.add(appEntry);
+
+        mController.setExternalAudioBytes(EXTRA_AUDIO_BYTES);
+        mController.onDataLoaded(apps);
+        // even when the manager notifies the controller again on data loaded the preference
+        // count should remain the same if new appEntries are not added.
+        mController.onDataLoaded(apps);
+
+        assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getIcon()).isEqualTo(
+                mContext.getDrawable(R.drawable.test_icon));
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getTitle()).isEqualTo(LABEL);
+        assertThat(mLogicalPreferenceGroup.getPreference(0).getSummary()).isEqualTo(SIZE_STR);
+
+        assertThat(mLogicalPreferenceGroup.getPreference(1).getIcon()).isEqualTo(
+                mContext.getDrawable(R.drawable.ic_headset));
+        assertThat(mLogicalPreferenceGroup.getPreference(1).getTitle()).isEqualTo(
+                mContext.getString(R.string.storage_audio_files_title));
+        assertThat(mLogicalPreferenceGroup.getPreference(1).getSummary()).isEqualTo(
+                Long.toString(EXTRA_AUDIO_BYTES));
+    }
+
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageMediaCategoryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageMediaCategoryPreferenceControllerTest.java
new file mode 100644
index 0000000..a91ca10
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageMediaCategoryPreferenceControllerTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ProgressBarPreference;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link StorageMediaCategoryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class StorageMediaCategoryPreferenceControllerTest {
+
+    @Test
+    public void handlePreferenceClicked_shouldLaunchAccountSyncDetailsFragment() {
+        Context context = RuntimeEnvironment.application;
+        ProgressBarPreference progressBarPreference = new ProgressBarPreference(context);
+        PreferenceControllerTestHelper<StorageMediaCategoryPreferenceController> helper =
+                new PreferenceControllerTestHelper<>(context,
+                        StorageMediaCategoryPreferenceController.class, progressBarPreference);
+        FragmentController mMockFragmentController = helper.getMockFragmentController();
+        helper.markState(Lifecycle.State.CREATED);
+
+        progressBarPreference.performClick();
+
+        verify(mMockFragmentController).launchFragment(
+                any(StorageMediaCategoryDetailFragment.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageSettingsManagerTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageSettingsManagerTest.java
new file mode 100644
index 0000000..4174d07
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageSettingsManagerTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+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.os.Bundle;
+import android.os.storage.VolumeInfo;
+import android.util.SparseArray;
+
+import androidx.loader.app.LoaderManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link StorageSettingsManager}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class StorageSettingsManagerTest {
+
+    private Context mContext;
+    private VolumeInfo mVolumeInfo;
+    private StorageSettingsManager mStorageSettingsManager;
+    @Captor
+    private ArgumentCaptor<LoaderManager
+            .LoaderCallbacks<SparseArray<StorageAsyncLoader.AppsStorageResult>>> mAppsStorageResult;
+    @Captor
+    private ArgumentCaptor<LoaderManager.LoaderCallbacks<PrivateStorageInfo>> mVolumeSizeCallback;
+
+    @Mock
+    private StorageSettingsManager.VolumeListener mVolumeListener1;
+
+    @Mock
+    private StorageSettingsManager.VolumeListener mVolumeListener2;
+
+    @Mock
+    private LoaderManager mLoaderManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mStorageSettingsManager = new StorageSettingsManager(mContext, mVolumeInfo);
+        mStorageSettingsManager.startLoading(mLoaderManager);
+        verify(mLoaderManager, times(1)).restartLoader(eq(0), eq(Bundle.EMPTY),
+                mAppsStorageResult.capture());
+        verify(mLoaderManager, times(1)).restartLoader(eq(1), eq(Bundle.EMPTY),
+                mVolumeSizeCallback.capture());
+    }
+
+    @Test
+    public void volumeSizeCallback_onLoadFinished_listenerOnSizeCalculatedCalled()
+            throws Exception {
+        mStorageSettingsManager.registerListener(mVolumeListener1);
+        mStorageSettingsManager.registerListener(mVolumeListener2);
+
+        StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
+        when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
+        when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> data = new SparseArray<>();
+        mAppsStorageResult.getValue().onLoadFinished(null, data);
+
+        PrivateStorageInfo storageInfo = new VolumeSizesLoader(mContext, storageVolumeProvider,
+                null, mVolumeInfo).loadInBackground();
+
+        mVolumeSizeCallback.getValue().onLoadFinished(null, storageInfo);
+
+        verify(mVolumeListener1, times(1)).onDataLoaded(data, 9000L, 10000L);
+        verify(mVolumeListener2, times(1)).onDataLoaded(data, 9000L, 10000L);
+    }
+
+    @Test
+    public void appsStorageResult_unregisterListener_onlyOneListenerOnDataLoadedCalled()
+            throws Exception {
+        mStorageSettingsManager.registerListener(mVolumeListener1);
+        mStorageSettingsManager.registerListener(mVolumeListener2);
+        mStorageSettingsManager.unregisterlistener(mVolumeListener2);
+        SparseArray<StorageAsyncLoader.AppsStorageResult> data = new SparseArray<>();
+        mAppsStorageResult.getValue().onLoadFinished(null, data);
+
+        StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
+        when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
+        when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
+
+        PrivateStorageInfo storageInfo = new VolumeSizesLoader(mContext, storageVolumeProvider,
+                null, mVolumeInfo).loadInBackground();
+
+        mVolumeSizeCallback.getValue().onLoadFinished(null, storageInfo);
+
+        verify(mVolumeListener1, times(1)).onDataLoaded(data, 9000L, 10000L);
+        verify(mVolumeListener2, never()).onDataLoaded(data, 9000L, 10000L);
+    }
+
+    @Test
+    public void onReceivedSizes_storageResultNotLoaded_noListenersCalled()
+            throws Exception {
+        mStorageSettingsManager.registerListener(mVolumeListener1);
+        mStorageSettingsManager.registerListener(mVolumeListener2);
+        SparseArray<StorageAsyncLoader.AppsStorageResult> data = new SparseArray<>();
+
+        StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
+        when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
+        when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
+
+        PrivateStorageInfo storageInfo = new VolumeSizesLoader(mContext, storageVolumeProvider,
+                null, mVolumeInfo).loadInBackground();
+        mVolumeSizeCallback.getValue().onLoadFinished(null, storageInfo);
+
+        verify(mVolumeListener1, never()).onDataLoaded(data, 9000L, 10000L);
+        verify(mVolumeListener2, never()).onDataLoaded(data, 9000L, 10000L);
+    }
+
+    @Test
+    public void onReceivedSizes_volumeSizeNotLoaded_noListenersCalled()
+            throws Exception {
+        mStorageSettingsManager.registerListener(mVolumeListener1);
+        mStorageSettingsManager.registerListener(mVolumeListener2);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> data = new SparseArray<>();
+        mAppsStorageResult.getValue().onLoadFinished(null, data);
+
+        StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
+        when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
+        when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
+
+        PrivateStorageInfo storageInfo = new VolumeSizesLoader(mContext, storageVolumeProvider,
+                null, mVolumeInfo).loadInBackground();
+
+        verify(mVolumeListener1, never()).onDataLoaded(data, 9000L, 10000L);
+        verify(mVolumeListener2, never()).onDataLoaded(data, 9000L, 10000L);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageSizeBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageSizeBasePreferenceControllerTest.java
new file mode 100644
index 0000000..bc63120
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageSizeBasePreferenceControllerTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.usage.StorageStats;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.applications.StorageStatsSource;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link StorageSizeBasePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class StorageSizeBasePreferenceControllerTest {
+
+    private StorageAppDetailPreference mStorageAppDetailPreference;
+    private TestStorageSizeBasePreferenceController mController;
+    private PreferenceControllerTestHelper<TestStorageSizeBasePreferenceController>
+            mPreferenceControllerHelper;
+
+    @Mock
+    private AppsStorageStatsManager mAppsStorageStatsManager;
+
+    private static class TestStorageSizeBasePreferenceController extends
+            StorageSizeBasePreferenceController {
+
+        TestStorageSizeBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected long getSize() {
+            return 1_000_000_000;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        mStorageAppDetailPreference = new StorageAppDetailPreference(context);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
+                TestStorageSizeBasePreferenceController.class, mStorageAppDetailPreference);
+        mController = mPreferenceControllerHelper.getController();
+    }
+
+    @Test
+    public void refreshUi_defaultState_nothingIsSet() {
+        assertThat(mStorageAppDetailPreference.getDetailText()).isNull();
+        assertThat(mController.isCachedCleared()).isFalse();
+        assertThat(mController.isDataCleared()).isFalse();
+        assertThat(mController.getAppStorageStats()).isNull();
+    }
+
+    @Test
+    public void setAppsStorageStatsManager_shouldRegisterController() {
+        mController.setAppsStorageStatsManager(mAppsStorageStatsManager);
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+
+        verify(mAppsStorageStatsManager).registerListener(mController);
+    }
+
+    @Test
+    public void onDataLoaded_shouldUpdateCachedAndDataClearedState() {
+        mController.onDataLoaded(null, true, true);
+
+        assertThat(mController.isCachedCleared()).isTrue();
+        assertThat(mController.isDataCleared()).isTrue();
+    }
+
+    @Test
+    public void onDataLoaded_appStorageStatsNotSet_shouldNotUpdateDetailText() {
+        mController.onDataLoaded(null, true, true);
+
+        assertThat(mController.getAppStorageStats()).isNull();
+        assertThat(mStorageAppDetailPreference.getDetailText()).isNull();
+    }
+
+    @Test
+    public void onDataLoaded_appStorageStatsSet_shouldUpdateDetailText() {
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+
+        StorageStats stats = new StorageStats();
+        StorageStatsSource.AppStorageStats storageStats =
+                new StorageStatsSource.AppStorageStatsImpl(stats);
+        mController.setAppStorageStats(storageStats);
+        mController.onDataLoaded(null, true, true);
+
+        assertThat(mController.getAppStorageStats()).isNotNull();
+        assertThat(mStorageAppDetailPreference.getDetailText()).isEqualTo("1.00 GB");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageSystemCategoryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageSystemCategoryPreferenceControllerTest.java
new file mode 100644
index 0000000..97e99e0
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageSystemCategoryPreferenceControllerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ProgressBarPreference;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowApplication;
+
+/** Unit test for {@link StorageSystemCategoryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class StorageSystemCategoryPreferenceControllerTest {
+
+    @Test
+    public void handlePreferenceClicked_openAlertDialog() {
+        Context context = RuntimeEnvironment.application;
+        ProgressBarPreference progressBarPreference = new ProgressBarPreference(context);
+        PreferenceControllerTestHelper<StorageSystemCategoryPreferenceController>
+                preferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
+                StorageSystemCategoryPreferenceController.class, progressBarPreference);
+        preferenceControllerHelper.markState(Lifecycle.State.CREATED);
+        progressBarPreference.performClick();
+
+        ShadowAlertDialog dialog = ShadowApplication.getInstance().getLatestAlertDialog();
+
+        assertThat(dialog.getMessage().toString()).contains(
+                context.getResources().getString(R.string.storage_detail_dialog_system,
+                        Build.VERSION.RELEASE));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageUsageBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageUsageBasePreferenceControllerTest.java
new file mode 100644
index 0000000..c44ee03
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageUsageBasePreferenceControllerTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.android.car.settings.storage.FileSizeFormatter.MEGABYTE_IN_BYTES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.util.SparseArray;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ProgressBarPreference;
+import com.android.settingslib.applications.StorageStatsSource;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link StorageUsageBasePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class StorageUsageBasePreferenceControllerTest {
+
+    private Context mContext;
+    private ProgressBarPreference mProgressBarPreference;
+    private TestStorageUsageBasePreferenceController mController;
+    private PreferenceControllerTestHelper<TestStorageUsageBasePreferenceController>
+            mPreferenceControllerHelper;
+
+    private static class TestStorageUsageBasePreferenceController extends
+            StorageUsageBasePreferenceController {
+
+        TestStorageUsageBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        public long calculateCategoryUsage(
+                SparseArray<StorageAsyncLoader.AppsStorageResult> result, long usedSizeBytes) {
+            return 1_000_000_000;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mProgressBarPreference = new ProgressBarPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestStorageUsageBasePreferenceController.class, mProgressBarPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void refreshUi_defaultInitialize_hasDefaultSummary() {
+        assertThat(mProgressBarPreference.getSummary().toString())
+                .isEqualTo(mContext.getString(R.string.memory_calculating_size));
+    }
+
+    @Test
+    public void onDataLoaded_setsSummary() {
+        SparseArray<StorageAsyncLoader.AppsStorageResult> results = new SparseArray<>();
+        StorageAsyncLoader.AppsStorageResult result =
+                new StorageAsyncLoader.AppsStorageResult(0, 0, 0, 0, 0);
+        result.setExternalStats(
+                new StorageStatsSource.ExternalStorageStats(
+                        MEGABYTE_IN_BYTES * 500, // total
+                        MEGABYTE_IN_BYTES * 100, // audio
+                        MEGABYTE_IN_BYTES * 150, // video
+                        MEGABYTE_IN_BYTES * 200, 0)); // image
+        results.put(0, result);
+        mController.onDataLoaded(results, 100, 100);
+
+        assertThat(mProgressBarPreference.getSummary().toString()).isEqualTo("1.0 GB");
+    }
+
+    @Test
+    public void onDataLoaded_setProgressBarPercentage() {
+        SparseArray<StorageAsyncLoader.AppsStorageResult> results = new SparseArray<>();
+        StorageAsyncLoader.AppsStorageResult result =
+                new StorageAsyncLoader.AppsStorageResult(0, 0, 0, 0, 0);
+        result.setExternalStats(
+                new StorageStatsSource.ExternalStorageStats(
+                        MEGABYTE_IN_BYTES * 500, // total
+                        MEGABYTE_IN_BYTES * 100, // audio
+                        MEGABYTE_IN_BYTES * 150, // video
+                        MEGABYTE_IN_BYTES * 200, 0)); // image
+        results.put(0, result);
+        mController.onDataLoaded(results, 100, 2_000_000_000);
+        // usage size is half the total size i.e percentage of storage used should be 50.
+        assertThat(mProgressBarPreference.getProgress()).isEqualTo(50);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/StorageUtilsTest.java b/tests/robotests/src/com/android/car/settings/storage/StorageUtilsTest.java
new file mode 100644
index 0000000..17964d8
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/StorageUtilsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static android.os.storage.VolumeInfo.STATE_MOUNTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit test for {@link StorageUtils}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class StorageUtilsTest {
+
+    @Mock
+    private StorageManager mStorageManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void maybeInitializeVolume_mounted_shouldReturnVolumeInfo() {
+        VolumeInfo volumeInfo = new VolumeInfo("id", VolumeInfo.TYPE_PRIVATE, null, "id");
+        volumeInfo.state = STATE_MOUNTED;
+
+        when(mStorageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)).thenReturn(volumeInfo);
+
+        VolumeInfo expected = StorageUtils.maybeInitializeVolume(mStorageManager, null);
+
+        assertThat(expected).isEqualTo(volumeInfo);
+    }
+
+    @Test
+    public void maybeInitializeVolume_notMounted_shouldNotReturnVolumeInfo() {
+        VolumeInfo volumeInfo = new VolumeInfo("id", VolumeInfo.TYPE_PRIVATE, null, "id");
+
+        when(mStorageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)).thenReturn(volumeInfo);
+
+        VolumeInfo expected = StorageUtils.maybeInitializeVolume(mStorageManager, null);
+
+        assertThat(expected).isNull();
+    }
+
+    @Test
+    public void maybeInitializeVolume_differentType_shouldNotReturnVolumeInfo() {
+        VolumeInfo volumeInfo = new VolumeInfo("id", VolumeInfo.TYPE_EMULATED, null, "id");
+
+        when(mStorageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)).thenReturn(volumeInfo);
+
+        VolumeInfo expected = StorageUtils.maybeInitializeVolume(mStorageManager, null);
+
+        assertThat(expected).isNull();
+    }
+
+    @Test
+    public void maybeInitializeVolume_defaultId_notMounted_returnsNull() {
+        VolumeInfo volumeInfo = new VolumeInfo("id", VolumeInfo.TYPE_EMULATED, null, "id");
+        Bundle bundle = new Bundle();
+        when(mStorageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)).thenReturn(volumeInfo);
+
+        VolumeInfo expected = StorageUtils.maybeInitializeVolume(mStorageManager, bundle);
+
+        assertThat(expected).isNull();
+    }
+
+    @Test
+    public void maybeInitializeVolume_getDefaultIdFromBundle_mounted_shouldReturnVolumeInfo() {
+        VolumeInfo volumeInfo = new VolumeInfo("id", VolumeInfo.TYPE_PRIVATE, null, "id");
+        volumeInfo.state = STATE_MOUNTED;
+        Bundle bundle = new Bundle();
+        when(mStorageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)).thenReturn(volumeInfo);
+
+        VolumeInfo expected = StorageUtils.maybeInitializeVolume(mStorageManager, bundle);
+
+        assertThat(expected).isEqualTo(volumeInfo);
+    }
+
+    @Test
+    public void maybeInitializeVolume_getIdFromBundle_shouldReturnVolumeInfo() {
+        VolumeInfo volumeInfo = new VolumeInfo("id", VolumeInfo.TYPE_PRIVATE, null, "id");
+        volumeInfo.state = STATE_MOUNTED;
+        Bundle bundle = new Bundle();
+        bundle.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_EMULATED_INTERNAL);
+        when(mStorageManager.findVolumeById(VolumeInfo.ID_EMULATED_INTERNAL)).thenReturn(
+                volumeInfo);
+
+        VolumeInfo expected = StorageUtils.maybeInitializeVolume(mStorageManager, bundle);
+
+        assertThat(expected).isEqualTo(volumeInfo);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/storage/VolumeSizesLoaderTest.java b/tests/robotests/src/com/android/car/settings/storage/VolumeSizesLoaderTest.java
new file mode 100644
index 0000000..bd5eb2c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/storage/VolumeSizesLoaderTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.storage.VolumeInfo;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link VolumeSizesLoader}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class VolumeSizesLoaderTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @Test
+    public void getVolumeSize_getsValidSizes() throws Exception {
+        VolumeInfo info = mock(VolumeInfo.class);
+        StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
+        when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
+        when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
+
+        PrivateStorageInfo storageInfo = new VolumeSizesLoader(mContext, storageVolumeProvider,
+                null, info).loadInBackground();
+
+        assertThat(storageInfo.freeBytes).isEqualTo(1000L);
+        assertThat(storageInfo.totalBytes).isEqualTo(10000L);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/suggestions/SuggestionPreferenceTest.java b/tests/robotests/src/com/android/car/settings/suggestions/SuggestionPreferenceTest.java
new file mode 100644
index 0000000..cc1478f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/suggestions/SuggestionPreferenceTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 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.car.settings.suggestions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.ShapeDrawable;
+import android.service.settings.suggestions.Suggestion;
+import android.view.View;
+
+import androidx.appcompat.view.ContextThemeWrapper;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit test for {@link SuggestionPreference}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class SuggestionPreferenceTest {
+
+    private static final String SUGGESTION_ID = "id";
+
+    @Mock
+    private SuggestionPreference.Callback mCallback;
+    private Suggestion mSuggestion;
+    private PreferenceViewHolder mHolder;
+    private SuggestionPreference mPref;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+
+        mSuggestion = new Suggestion.Builder(SUGGESTION_ID).build();
+        Context themedContext = new ContextThemeWrapper(context, R.style.CarSettingTheme);
+        View rootView = View.inflate(themedContext, R.layout.suggestion_preference, null);
+        mHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+        mPref = new SuggestionPreference(context, mSuggestion, mCallback);
+    }
+
+    @Test
+    public void getSuggestion_returnsSuggestion() {
+        assertThat(mPref.getSuggestion()).isEqualTo(mSuggestion);
+    }
+
+    @Test
+    public void updateSuggestion_updatesPreference() {
+        Icon icon = mock(Icon.class);
+        Drawable iconDrawable = new ShapeDrawable();
+        when(icon.loadDrawable(any(Context.class))).thenReturn(iconDrawable);
+        String title = "title";
+        String summary = "summary";
+        Suggestion updatedSuggestion = new Suggestion.Builder(SUGGESTION_ID).setIcon(icon).setTitle(
+                title).setSummary(summary).build();
+
+        mPref.updateSuggestion(updatedSuggestion);
+
+        assertThat(mPref.getSuggestion()).isEqualTo(updatedSuggestion);
+        assertThat(mPref.getIcon()).isEqualTo(iconDrawable);
+        assertThat(mPref.getTitle()).isEqualTo(title);
+        assertThat(mPref.getSummary()).isEqualTo(summary);
+    }
+
+    @Test
+    public void updateSuggestion_idMismatch_throwsIllegalArgumentException() {
+        Suggestion updatedSuggestion = new Suggestion.Builder(SUGGESTION_ID + "wrong id").build();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mPref.updateSuggestion(updatedSuggestion));
+    }
+
+    @Test
+    public void getKey_includesSuggestionId() {
+        assertThat(mPref.getKey()).isEqualTo(
+                SuggestionPreference.SUGGESTION_PREFERENCE_KEY + SUGGESTION_ID);
+    }
+
+    @Test
+    public void onClick_callsLaunchSuggestion() {
+        mPref.onBindViewHolder(mHolder);
+
+        mHolder.itemView.performClick();
+
+        verify(mCallback).launchSuggestion(mPref);
+    }
+
+    @Test
+    public void dismissButtonClick_callsDismissSuggestion() {
+        mPref.onBindViewHolder(mHolder);
+
+        mHolder.findViewById(R.id.dismiss_button).performClick();
+
+        verify(mCallback).dismissSuggestion(mPref);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/suggestions/SuggestionsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/suggestions/SuggestionsPreferenceControllerTest.java
new file mode 100644
index 0000000..6dbb518
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/suggestions/SuggestionsPreferenceControllerTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2018 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.car.settings.suggestions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.suggestions.SuggestionController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Unit test for {@link SuggestionsPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class SuggestionsPreferenceControllerTest {
+
+    private static final Suggestion SUGGESTION_1 = new Suggestion.Builder("1").build();
+    private static final Suggestion SUGGESTION_2 = new Suggestion.Builder("2").build();
+
+    @Mock
+    private LoaderManager mLoaderManager;
+    @Mock
+    private Loader<List<Suggestion>> mLoader;
+    @Mock
+    private SuggestionController mSuggestionController;
+    private Context mContext;
+    private PreferenceGroup mGroup;
+    private PreferenceControllerTestHelper<SuggestionsPreferenceController> mControllerHelper;
+    private SuggestionsPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mGroup = new PreferenceCategory(mContext);
+
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                SuggestionsPreferenceController.class);
+        mController = mControllerHelper.getController();
+        mController.setLoaderManager(mLoaderManager);
+        ReflectionHelpers.setField(SuggestionsPreferenceController.class, mController,
+                "mSuggestionController", mSuggestionController);
+        mControllerHelper.setPreference(mGroup);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+    }
+
+    @Test
+    public void setPreference_loaderManagerSet_doesNothing() {
+        PreferenceControllerTestHelper<SuggestionsPreferenceController> helper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        SuggestionsPreferenceController.class);
+        mController = helper.getController();
+        mController.setLoaderManager(mLoaderManager);
+
+        helper.setPreference(new PreferenceCategory(mContext));
+    }
+
+    @Test
+    public void checkInitialized_nullLoaderManager_throwsIllegalStateException() {
+        PreferenceControllerTestHelper<SuggestionsPreferenceController> helper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        SuggestionsPreferenceController.class);
+
+        assertThrows(IllegalStateException.class,
+                () -> helper.setPreference(new PreferenceCategory(mContext)));
+    }
+
+    @Test
+    public void onStart_noSuggestions_hidesGroup() {
+        assertThat(mGroup.isVisible()).isFalse();
+    }
+
+    @Test
+    public void onStart_startsSuggestionController() {
+        verify(mSuggestionController).start();
+    }
+
+    @Test
+    public void onStop_stopsSuggestionController() {
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        verify(mSuggestionController).stop();
+    }
+
+    @Test
+    public void onStop_destroysLoader() {
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        verify(mLoaderManager).destroyLoader(SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS);
+    }
+
+    @Test
+    public void onServiceConnected_restartsLoader() {
+        mController.onServiceConnected();
+
+        verify(mLoaderManager).restartLoader(
+                SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS, /* args= */ null, mController);
+    }
+
+    @Test
+    public void onServiceDisconnected_destroysLoader() {
+        mController.onServiceDisconnected();
+
+        verify(mLoaderManager).destroyLoader(SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS);
+    }
+
+    @Test
+    public void onCreateLoader_returnsSettingsSuggestionsLoader() {
+        assertThat(mController.onCreateLoader(
+                SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS, /* args= */ null)).isInstanceOf(
+                SettingsSuggestionsLoader.class);
+    }
+
+    @Test
+    public void onCreateLoader_unsupportedId_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () -> mController.onCreateLoader(
+                SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS + 1000, /* args= */ null));
+    }
+
+    @Test
+    public void onLoadFinished_groupContainsSuggestionPreference() {
+        mController.onLoadFinished(mLoader, Collections.singletonList(SUGGESTION_1));
+
+        assertThat(mGroup.getPreferenceCount()).isEqualTo(1);
+        Preference addedPref = mGroup.getPreference(0);
+        assertThat(addedPref).isInstanceOf(SuggestionPreference.class);
+    }
+
+    @Test
+    public void onLoadFinished_newSuggestion_addsToGroup() {
+        mController.onLoadFinished(mLoader, Collections.singletonList(SUGGESTION_1));
+        assertThat(mGroup.getPreferenceCount()).isEqualTo(1);
+
+        mController.onLoadFinished(mLoader, Arrays.asList(SUGGESTION_1, SUGGESTION_2));
+
+        assertThat(mGroup.getPreferenceCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void onLoadFinished_removedSuggestion_removesFromGroup() {
+        mController.onLoadFinished(mLoader, Arrays.asList(SUGGESTION_1, SUGGESTION_2));
+        assertThat(mGroup.getPreferenceCount()).isEqualTo(2);
+
+        mController.onLoadFinished(mLoader, Collections.singletonList(SUGGESTION_2));
+
+        assertThat(mGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(((SuggestionPreference) mGroup.getPreference(0)).getSuggestion()).isEqualTo(
+                SUGGESTION_2);
+    }
+
+    @Test
+    public void onLoadFinished_noSuggestions_hidesGroup() {
+        mController.onLoadFinished(mLoader, Collections.singletonList(SUGGESTION_1));
+        assertThat(mGroup.isVisible()).isTrue();
+
+        mController.onLoadFinished(mLoader, Collections.emptyList());
+
+        assertThat(mGroup.isVisible()).isFalse();
+    }
+
+    @Test
+    public void launchSuggestion_sendsPendingIntent() throws PendingIntent.CanceledException {
+        PendingIntent pendingIntent = mock(PendingIntent.class);
+        Suggestion suggestion = new Suggestion.Builder("1").setPendingIntent(pendingIntent).build();
+        SuggestionPreference preference = new SuggestionPreference(mContext,
+                suggestion, /* callback= */ null);
+
+        mController.launchSuggestion(preference);
+
+        verify(pendingIntent).send();
+    }
+
+    @Test
+    public void launchSuggestion_callsSuggestionControllerLaunch() {
+        PendingIntent pendingIntent = mock(PendingIntent.class);
+        Suggestion suggestion = new Suggestion.Builder("1").setPendingIntent(pendingIntent).build();
+        SuggestionPreference preference = new SuggestionPreference(mContext,
+                suggestion, /* callback= */ null);
+
+        mController.launchSuggestion(preference);
+
+        verify(mSuggestionController).launchSuggestion(suggestion);
+    }
+
+    @Test
+    public void dismissSuggestion_removesSuggestion() {
+        mController.onLoadFinished(mLoader, Arrays.asList(SUGGESTION_1, SUGGESTION_2));
+        assertThat(mGroup.getPreferenceCount()).isEqualTo(2);
+        SuggestionPreference pref = (SuggestionPreference) mGroup.getPreference(0);
+
+        mController.dismissSuggestion(pref);
+
+        assertThat(mGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(((SuggestionPreference) mGroup.getPreference(0)).getSuggestion()).isEqualTo(
+                SUGGESTION_2);
+    }
+
+    @Test
+    public void dismissSuggestion_lastSuggestion_hidesGroup() {
+        mController.onLoadFinished(mLoader, Collections.singletonList(SUGGESTION_1));
+        SuggestionPreference pref = (SuggestionPreference) mGroup.getPreference(0);
+
+        mController.dismissSuggestion(pref);
+
+        assertThat(mGroup.isVisible()).isFalse();
+    }
+
+    @Test
+    public void dismissSuggestion_callsSuggestionControllerDismiss() {
+        mController.onLoadFinished(mLoader, Collections.singletonList(SUGGESTION_1));
+        SuggestionPreference pref = (SuggestionPreference) mGroup.getPreference(0);
+
+        mController.dismissSuggestion(pref);
+
+        verify(mSuggestionController).dismissSuggestions(pref.getSuggestion());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/BluetoothMacAddressPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/BluetoothMacAddressPreferenceControllerTest.java
new file mode 100644
index 0000000..ade3c89
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/BluetoothMacAddressPreferenceControllerTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowBluetoothPan;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link BluetoothMacAddressPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothPan.class})
+public class BluetoothMacAddressPreferenceControllerTest {
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<BluetoothMacAddressPreferenceController>
+            mControllerHelper;
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+
+        // Construct controller.
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+            BluetoothMacAddressPreferenceController.class, mPreference);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowBluetoothAdapter.reset();
+    }
+
+    @Test
+    public void refreshUi_setsAddress() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+
+        // Make sure controller is available.
+        BluetoothAdapter.getDefaultAdapter().enable();
+        getShadowBluetoothAdapter().setState(BluetoothAdapter.STATE_ON);
+
+        String address = "address";
+        getShadowBluetoothAdapter().setAddress(address);
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(address);
+    }
+
+    @Test
+    public void getAvailabilityStatus_featureBluetooth_unsupportedOnDevice() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ false);
+
+        assertThat(mControllerHelper.getController().getAvailabilityStatus()).isEqualTo(
+                UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_enableDefaultAdapter_available() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                FEATURE_BLUETOOTH, /* supported= */ true);
+
+        assertThat(mControllerHelper.getController().getAvailabilityStatus()).isEqualTo(
+                AVAILABLE);
+    }
+
+    private ShadowBluetoothAdapter getShadowBluetoothAdapter() {
+        return (ShadowBluetoothAdapter) Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+    }
+}
+
diff --git a/tests/robotests/src/com/android/car/settings/system/BuildNumberPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/BuildNumberPreferenceControllerTest.java
new file mode 100644
index 0000000..f9e9a6c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/BuildNumberPreferenceControllerTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.development.DevelopmentSettingsUtil;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowToast;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class BuildNumberPreferenceControllerTest {
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<BuildNumberPreferenceController>
+            mPreferenceControllerHelper;
+    private BuildNumberPreferenceController mController;
+    private Preference mPreference;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                BuildNumberPreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+
+        // By default, user is an admin user.
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+
+        // By default, no restrictions on debugging features.
+        when(mCarUserManagerHelper.getCurrentProcessUserInfo()).thenReturn(new UserInfo());
+        when(mCarUserManagerHelper.hasUserRestriction(eq(UserManager.DISALLOW_DEBUGGING_FEATURES),
+                any(UserInfo.class))).thenReturn(false);
+
+        // By default device is provisioned.
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 1);
+
+        // By default development settings is disabled.
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_RESUME);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_notProvisioned_returnFalse() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0);
+        assertThat(mController.handlePreferenceClicked(mPreference)).isFalse();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_nonAdmin_returnFalse() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+
+        assertThat(mController.handlePreferenceClicked(mPreference)).isFalse();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_demoUser_returnsTrue() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(true);
+
+        assertThat(mController.handlePreferenceClicked(mPreference)).isTrue();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_adminUser_returnsTrue() {
+        assertThat(mController.handlePreferenceClicked(mPreference)).isTrue();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_devSettingsDisabled_firstClick_noToast() {
+        mPreference.performClick();
+        assertThat(ShadowToast.shownToastCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_devSettingsDisabled_someClicks_showToast() {
+        for (int i = 0; i < getTapsToShowToast(); i++) {
+            mPreference.performClick();
+        }
+
+        int remainingClicks = getTapsToBecomeDeveloper() - getTapsToShowToast();
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getResources().getQuantityString(R.plurals.show_dev_countdown,
+                        remainingClicks, remainingClicks));
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_devSettingsDisabled_allClicks_showDevEnabledToast() {
+        for (int i = 0; i < getTapsToBecomeDeveloper(); i++) {
+            mPreference.performClick();
+        }
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.show_dev_on));
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_devSettingsDisabled_allClicks_devSettingsEnabled() {
+        for (int i = 0; i < getTapsToBecomeDeveloper(); i++) {
+            mPreference.performClick();
+        }
+        assertThat(DevelopmentSettingsUtil.isDevelopmentSettingsEnabled(mContext,
+                mCarUserManagerHelper)).isTrue();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_devSettingsDisabled_extraClicks_noAlreadyDevToast() {
+        int extraClicks = 100;
+        for (int i = 0; i < getTapsToBecomeDeveloper() + extraClicks; i++) {
+            mPreference.performClick();
+        }
+        assertThat(
+                ShadowToast.showedToast(mContext.getString(R.string.show_dev_already))).isFalse();
+    }
+
+    @Test
+    public void testHandlePreferenceClicked_devSettingsEnabled_click_showAlreadyDevToast() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_PAUSE);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_RESUME);
+        mPreference.performClick();
+        assertThat(ShadowToast.showedToast(mContext.getString(R.string.show_dev_already))).isTrue();
+    }
+
+    private int getTapsToBecomeDeveloper() {
+        return mContext.getResources().getInteger(R.integer.enable_developer_settings_click_count);
+    }
+
+    private int getTapsToShowToast() {
+        return mContext.getResources().getInteger(
+                R.integer.enable_developer_settings_clicks_to_show_toast_count);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/DeveloperOptionsEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/DeveloperOptionsEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..6f3a29b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/DeveloperOptionsEntryPreferenceControllerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class DeveloperOptionsEntryPreferenceControllerTest {
+
+    private Context mContext;
+    private DeveloperOptionsEntryPreferenceController mController;
+    private UserInfo mUserInfo;
+    @Mock
+    private CarUserManagerHelper mShadowCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mShadowCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+        mController = new PreferenceControllerTestHelper<>(mContext,
+                DeveloperOptionsEntryPreferenceController.class,
+                new Preference(mContext)).getController();
+
+        // Setup admin user who is able to enable developer settings.
+        mUserInfo = new UserInfo();
+        when(mShadowCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+        when(mShadowCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+        when(mShadowCarUserManagerHelper.getCurrentProcessUserInfo()).thenReturn(mUserInfo);
+        new CarUserManagerHelper(mContext).setUserRestriction(mUserInfo,
+                UserManager.DISALLOW_DEBUGGING_FEATURES, false);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testGetAvailabilityStatus_devOptionsEnabled_isAvailable() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void testGetAvailabilityStatus_devOptionsDisabled_isUnavailable() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void testGetAvailabilityStatus_devOptionsEnabled_hasUserRestriction_isUnavailable() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+        new CarUserManagerHelper(mContext).setUserRestriction(mUserInfo,
+                UserManager.DISALLOW_DEBUGGING_FEATURES, true);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/MasterClearAccountsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/MasterClearAccountsPreferenceControllerTest.java
new file mode 100644
index 0000000..9f582a2
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/MasterClearAccountsPreferenceControllerTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.UserManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowAccountManager;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit test for {@link MasterClearAccountsPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserManager.class,
+        ShadowAccountManager.class})
+public class MasterClearAccountsPreferenceControllerTest {
+
+    private static final int USER_ID = 111;
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<MasterClearAccountsPreferenceController>
+            mControllerHelper;
+    private MasterClearAccountsPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(USER_ID);
+
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new PreferenceCategory(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                MasterClearAccountsPreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowUserManager.reset();
+    }
+
+    @Test
+    public void onCreate_addsTitlePreference() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getTitle()).isEqualTo(
+                mContext.getString(R.string.master_clear_accounts));
+    }
+
+    @Test
+    public void refreshUi_accountsPresent_showsGroup() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        getShadowUserManager().addProfile(USER_ID, USER_ID,
+                String.valueOf(USER_ID), /* profileFlags= */ 0);
+        addAccountAndDescription(USER_ID, "accountName");
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_noAccountsPresent_hidesGroup() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        getShadowUserManager().addProfile(USER_ID, USER_ID,
+                String.valueOf(USER_ID), /* profileFlags= */ 0);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_multipleProfiles_showsAllAccounts() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        int profileId1 = 112;
+        getShadowUserManager().addProfile(USER_ID, profileId1,
+                String.valueOf(profileId1), /* profileFlags= */ 0);
+        String accountName1 = "accountName1";
+        addAccountAndDescription(profileId1, accountName1);
+
+        int profileId2 = 113;
+        getShadowUserManager().addProfile(USER_ID, profileId2,
+                String.valueOf(profileId2), /* profileFlags= */ 0);
+        String accountName2 = "accountName2";
+        addAccountAndDescription(profileId2, accountName2);
+
+        mController.refreshUi();
+
+        // Title + two profiles with one account each.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(3);
+        assertThat(mPreferenceGroup.getPreference(1).getTitle()).isEqualTo(accountName1);
+        assertThat(mPreferenceGroup.getPreference(2).getTitle()).isEqualTo(accountName2);
+    }
+
+    @Test
+    public void refreshUi_missingAccountDescription_skipsAccount() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        getShadowUserManager().addProfile(USER_ID, USER_ID,
+                String.valueOf(USER_ID), /* profileFlags= */ 0);
+        addAccountAndDescription(USER_ID, "account name with desc");
+        String accountNameNoDesc = "account name no desc";
+        getShadowAccountManager().addAccountAsUser(USER_ID,
+                new Account(accountNameNoDesc, accountNameNoDesc + "_type"));
+
+        mController.refreshUi();
+
+        // Title + one account with valid description.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+        assertThat(mPreferenceGroup.getPreference(1).getTitle()).isNotEqualTo(accountNameNoDesc);
+    }
+
+    @Test
+    public void refreshUi_accountAdded_addsPreferenceToGroup() {
+        getShadowUserManager().addProfile(USER_ID, USER_ID,
+                String.valueOf(USER_ID), /* profileFlags= */ 0);
+        addAccountAndDescription(USER_ID, "accountName");
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        String addedAccountName = "added account name";
+        addAccountAndDescription(USER_ID, addedAccountName);
+        mController.refreshUi();
+
+        // Title + one already present account + one newly added account.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(3);
+        assertThat(mPreferenceGroup.getPreference(2).getTitle()).isEqualTo(addedAccountName);
+    }
+
+    @Test
+    public void refreshUi_accountRemoved_removesPreferenceFromGroup() {
+        getShadowUserManager().addProfile(USER_ID, USER_ID,
+                String.valueOf(USER_ID), /* profileFlags= */ 0);
+        String accountNameToRemove = "account name to remove";
+        addAccountAndDescription(USER_ID, accountNameToRemove);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+
+        getShadowAccountManager().removeAllAccounts();
+        mController.refreshUi();
+
+        // Title only, all accounts removed.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+        assertThat(mPreferenceGroup.getPreference(0).getTitle()).isNotEqualTo(accountNameToRemove);
+    }
+
+    private void addAccountAndDescription(int profileId, String accountName) {
+        String type = accountName + "_type";
+        getShadowAccountManager().addAccountAsUser(profileId, new Account(accountName, type));
+        getShadowAccountManager().addAuthenticatorAsUser(profileId,
+                new AuthenticatorDescription(type, "packageName", /* labelId= */ 0, /* iconId= */
+                        0, /* smallIconId= */ 0, /* prefId= */ 0));
+    }
+
+    private ShadowUserManager getShadowUserManager() {
+        return Shadow.extract(UserManager.get(mContext));
+    }
+
+    private ShadowAccountManager getShadowAccountManager() {
+        return Shadow.extract(AccountManager.get(mContext));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/MasterClearConfirmFragmentTest.java b/tests/robotests/src/com/android/car/settings/system/MasterClearConfirmFragmentTest.java
new file mode 100644
index 0000000..183bb7d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/MasterClearConfirmFragmentTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.service.oemlock.OemLockManager;
+import android.service.persistentdata.PersistentDataBlockManager;
+import android.widget.Button;
+
+import androidx.preference.PreferenceManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowOemLockManager;
+import com.android.car.settings.testutils.ShadowPersistentDataBlockManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Map;
+
+/** Unit test for {@link MasterClearConfirmFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowPersistentDataBlockManager.class, ShadowOemLockManager.class})
+public class MasterClearConfirmFragmentTest {
+
+    private Context mContext;
+    private MasterClearConfirmFragment mFragment;
+
+    @Before
+    public void setUp() {
+        // Robolectric doesn't know about the pdb manager, so we must add it ourselves.
+        getSystemServiceMap().put(Context.PERSISTENT_DATA_BLOCK_SERVICE,
+                PersistentDataBlockManager.class.getName());
+        // Robolectric doesn't know about the oem lock manager, so we must add it ourselves.
+        getSystemServiceMap().put(Context.OEM_LOCK_SERVICE, OemLockManager.class.getName());
+
+        mContext = RuntimeEnvironment.application;
+        mFragment = FragmentController.of(new MasterClearConfirmFragment()).setup();
+
+        // Default to not provisioned.
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                0);
+    }
+
+    @After
+    public void tearDown() {
+        getSystemServiceMap().remove(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                0);
+    }
+
+    @Test
+    public void confirmClicked_sendsResetIntent() {
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        Intent resetIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(resetIntent.getAction()).isEqualTo(Intent.ACTION_FACTORY_RESET);
+        assertThat(resetIntent.getPackage()).isEqualTo("android");
+        assertThat(resetIntent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND).isEqualTo(
+                Intent.FLAG_RECEIVER_FOREGROUND);
+        assertThat(resetIntent.getExtras().getString(Intent.EXTRA_REASON)).isEqualTo(
+                "MasterClearConfirm");
+    }
+
+    @Test
+    public void confirmClicked_resetEsimFalse_resetIntentReflectsChoice() {
+        PreferenceManager.getDefaultSharedPreferences(mContext).edit().putBoolean(
+                mContext.getString(R.string.pk_master_clear_reset_esim), false).commit();
+
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        Intent resetIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(resetIntent.getExtras().getBoolean(Intent.EXTRA_WIPE_ESIMS)).isEqualTo(false);
+    }
+
+    @Test
+    public void confirmClicked_pdbManagerNull_sendsResetIntent() {
+        getSystemServiceMap().remove(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        Intent resetIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(resetIntent.getAction()).isEqualTo(Intent.ACTION_FACTORY_RESET);
+    }
+
+    @Test
+    public void confirmClicked_oemUnlockAllowed_doesNotWipePdb() {
+        getShadowOemLockManager().setIsOemUnlockAllowed(true);
+
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        assertThat(getShadowPdbManager().getWipeCalledCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void confirmClicked_oemUnlockAllowed_sendsResetIntent() {
+        getShadowOemLockManager().setIsOemUnlockAllowed(true);
+
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        Intent resetIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(resetIntent.getAction()).isEqualTo(Intent.ACTION_FACTORY_RESET);
+    }
+
+    @Test
+    public void confirmClicked_noOemUnlockAllowed_notProvisioned_doesNotWipePdb() {
+        getShadowOemLockManager().setIsOemUnlockAllowed(false);
+
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        assertThat(getShadowPdbManager().getWipeCalledCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void confirmClicked_noOemUnlockAllowed_notProvisioned_sendsResetIntent() {
+        getShadowOemLockManager().setIsOemUnlockAllowed(false);
+
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        Intent resetIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(resetIntent.getAction()).isEqualTo(Intent.ACTION_FACTORY_RESET);
+    }
+
+    @Test
+    public void confirmClicked_noOemUnlockAllowed_provisioned_wipesPdb() {
+        getShadowOemLockManager().setIsOemUnlockAllowed(false);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                1);
+
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        assertThat(getShadowPdbManager().getWipeCalledCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void confirmClicked_noOemUnlockAllowed_provisioned_sendsResetIntent() {
+        getShadowOemLockManager().setIsOemUnlockAllowed(false);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                1);
+
+        findMasterClearConfirmButton(mFragment.requireActivity()).performClick();
+
+        Intent resetIntent = ShadowApplication.getInstance().getBroadcastIntents().get(0);
+        assertThat(resetIntent.getAction()).isEqualTo(Intent.ACTION_FACTORY_RESET);
+    }
+
+    private Button findMasterClearConfirmButton(Activity activity) {
+        return activity.findViewById(R.id.action_button1);
+    }
+
+    private ShadowPersistentDataBlockManager getShadowPdbManager() {
+        return Shadow.extract(mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE));
+    }
+
+    private ShadowOemLockManager getShadowOemLockManager() {
+        return Shadow.extract(mContext.getSystemService(Context.OEM_LOCK_SERVICE));
+    }
+
+    private Map<String, String> getSystemServiceMap() {
+        return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/MasterClearEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/MasterClearEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..0843a59
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/MasterClearEntryPreferenceControllerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static android.os.UserManager.DISALLOW_FACTORY_RESET;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link MasterClearEntryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class MasterClearEntryPreferenceControllerTest {
+
+    private Context mContext;
+    private MasterClearEntryPreferenceController mController;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+
+        mController = new PreferenceControllerTestHelper<>(mContext,
+                MasterClearEntryPreferenceController.class,
+                new Preference(mContext)).getController();
+    }
+
+    @After
+    public void tearDown() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_DEMO_MODE, 0);
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_nonAdminUser_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_adminUser_available() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_adminUser_restricted_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_FACTORY_RESET)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_demoMode_demoUser_available() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_DEMO_MODE, 1);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_demoMode_demoUser_restricted_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_FACTORY_RESET)).thenReturn(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_DEMO_MODE, 1);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/MasterClearFragmentTest.java b/tests/robotests/src/com/android/car/settings/system/MasterClearFragmentTest.java
new file mode 100644
index 0000000..06b1025
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/MasterClearFragmentTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+
+import static com.android.car.settings.system.MasterClearFragment.CHECK_LOCK_REQUEST_CODE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.telephony.euicc.EuiccManager;
+import android.widget.Button;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.security.CheckLockActivity;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowEuiccManager;
+
+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;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Collections;
+import java.util.Map;
+
+/** Unit test for {@link MasterClearFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowEuiccManager.class})
+public class MasterClearFragmentTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    private MasterClearFragment mFragment;
+
+    @Before
+    public void setUp() {
+        // Setup needed by instantiated PreferenceControllers.
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.getAllSwitchableUsers()).thenReturn(Collections.emptyList());
+        // Robolectric doesn't know about the euicc manager, so we must add it ourselves.
+        getSystemServiceMap().put(Context.EUICC_SERVICE, EuiccManager.class.getName());
+
+        mFragment = FragmentController.of(new MasterClearFragment()).setup();
+    }
+
+    @After
+    public void tearDown() {
+        getSystemServiceMap().remove(Context.CARRIER_CONFIG_SERVICE);
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void masterClearButtonClicked_launchesCheckLockActivity() {
+        findMasterClearButton(mFragment.requireActivity()).performClick();
+
+        Intent startedIntent = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(startedIntent.getComponent().getClassName()).isEqualTo(
+                CheckLockActivity.class.getName());
+    }
+
+    @Test
+    public void processActivityResult_resultOk_launchesMasterClearConfirmFragment() {
+        mFragment.processActivityResult(CHECK_LOCK_REQUEST_CODE, RESULT_OK, /* data= */ null);
+
+        Fragment launchedFragment = mFragment.getFragmentManager().findFragmentById(
+                R.id.fragment_container);
+
+        assertThat(launchedFragment).isInstanceOf(MasterClearConfirmFragment.class);
+    }
+
+    @Test
+    public void processActivityResult_otherResultCode_doesNothing() {
+        mFragment.processActivityResult(CHECK_LOCK_REQUEST_CODE, RESULT_CANCELED, /* data= */ null);
+
+        Fragment launchedFragment = mFragment.getFragmentManager().findFragmentById(
+                R.id.fragment_container);
+
+        assertThat(launchedFragment).isInstanceOf(MasterClearFragment.class);
+    }
+
+    private Button findMasterClearButton(Activity activity) {
+        return activity.findViewById(R.id.action_button1);
+    }
+
+    private Map<String, String> getSystemServiceMap() {
+        return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/MasterClearOtherUsersPresentPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/MasterClearOtherUsersPresentPreferenceControllerTest.java
new file mode 100644
index 0000000..f6639a9
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/MasterClearOtherUsersPresentPreferenceControllerTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Collections;
+
+/** Unit test for {@link MasterClearOtherUsersPresentPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class MasterClearOtherUsersPresentPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private Preference mPreference;
+    private MasterClearOtherUsersPresentPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+
+        Context context = RuntimeEnvironment.application;
+        mPreference = new Preference(context);
+        PreferenceControllerTestHelper<MasterClearOtherUsersPresentPreferenceController>
+                controllerHelper = new PreferenceControllerTestHelper<>(context,
+                MasterClearOtherUsersPresentPreferenceController.class, mPreference);
+        controllerHelper.markState(Lifecycle.State.STARTED);
+        mController = controllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void refreshUi_noSwitchableUsers_hidesPreference() {
+        when(mCarUserManagerHelper.getAllSwitchableUsers()).thenReturn(Collections.emptyList());
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_switchableUsers_showsPreference() {
+        when(mCarUserManagerHelper.getAllSwitchableUsers()).thenReturn(
+                Collections.singletonList(new UserInfo()));
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isVisible()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/MasterClearResetEsimPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/MasterClearResetEsimPreferenceControllerTest.java
new file mode 100644
index 0000000..85826ce
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/MasterClearResetEsimPreferenceControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.telephony.euicc.EuiccManager;
+
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowEuiccManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Map;
+
+/** Unit test for {@link MasterClearResetEsimPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowEuiccManager.class})
+public class MasterClearResetEsimPreferenceControllerTest {
+
+    private Context mContext;
+    private MasterClearResetEsimPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        // Robolectric doesn't know about the euicc manager, so we must add it ourselves.
+        getSystemServiceMap().put(Context.EUICC_SERVICE, EuiccManager.class.getName());
+        mContext = RuntimeEnvironment.application;
+        ((ShadowEuiccManager) Shadow.extract(
+                mContext.getSystemService(Context.EUICC_SERVICE))).setIsEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1);
+
+        mController = new PreferenceControllerTestHelper<>(mContext,
+                MasterClearResetEsimPreferenceController.class,
+                new SwitchPreference(mContext)).getController();
+    }
+
+    @After
+    public void tearDown() {
+        getSystemServiceMap().remove(Context.CARRIER_CONFIG_SERVICE);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 0);
+    }
+
+    @Test
+    public void getAvailabilityStatus_showEsimPropertyTrue_available() {
+        SystemProperties.set(MasterClearResetEsimPreferenceController.KEY_SHOW_ESIM_RESET_CHECKBOX,
+                Boolean.TRUE.toString());
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_showEsimPropertyFalse_unsupportedOnDevice() {
+        SystemProperties.set(MasterClearResetEsimPreferenceController.KEY_SHOW_ESIM_RESET_CHECKBOX,
+                Boolean.FALSE.toString());
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    private Map<String, String> getSystemServiceMap() {
+        return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/RegulatoryInfoDisplayActivityTest.java b/tests/robotests/src/com/android/car/settings/system/RegulatoryInfoDisplayActivityTest.java
new file mode 100644
index 0000000..34ecb69
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/RegulatoryInfoDisplayActivityTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemProperties;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadows.ShadowAlertDialog;
+
+/** Unit test for {@link RegulatoryInfoDisplayActivity}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class RegulatoryInfoDisplayActivityTest {
+
+    private ActivityController<RegulatoryInfoDisplayActivity> mActivityController;
+    private RegulatoryInfoDisplayActivity mActivity;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mActivityController = ActivityController.of(new RegulatoryInfoDisplayActivity());
+        mActivity = mActivityController.get();
+        mActivityController.create();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowAlertDialog.reset();
+    }
+
+    @Test
+    public void getRegulatoryInfoImageFileName_skuIsNotEmpty() {
+        SystemProperties.set("ro.boot.hardware.sku", "test");
+
+        assertThat(mActivity.getRegulatoryInfoImageFileName())
+                .isEqualTo("/data/misc/elabel/regulatory_info_test.png");
+    }
+
+    @Test
+    public void getRegulatoryInfoImageFileName_skuIsEmpty() {
+        SystemProperties.set("ro.boot.hardware.sku", "");
+
+        assertThat(mActivity.getRegulatoryInfoImageFileName())
+                .isEqualTo("/data/misc/elabel/regulatory_info.png");
+    }
+
+    @Test
+    public void getSku_shouldReturnSystemProperty() {
+        String testSku = "test";
+        SystemProperties.set("ro.boot.hardware.sku", testSku);
+
+        assertThat(mActivity.getSku()).isEqualTo(testSku);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/ResetEsimPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/ResetEsimPreferenceControllerTest.java
new file mode 100644
index 0000000..71bc7fe
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/ResetEsimPreferenceControllerTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.telephony.euicc.EuiccManager;
+
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowEuiccManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Map;
+
+/** Unit test for {@link ResetEsimPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowEuiccManager.class})
+public class ResetEsimPreferenceControllerTest {
+
+    private Context mContext;
+    private ResetEsimPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        // Robolectric doesn't know about the euicc manager, so we must add it ourselves.
+        getSystemServiceMap().put(Context.EUICC_SERVICE, EuiccManager.class.getName());
+
+        mContext = RuntimeEnvironment.application;
+        mController = new PreferenceControllerTestHelper<>(mContext,
+                ResetEsimPreferenceController.class,
+                new SwitchPreference(mContext)).getController();
+    }
+
+    @After
+    public void tearDown() {
+        getSystemServiceMap().remove(Context.CARRIER_CONFIG_SERVICE);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 0);
+    }
+
+    @Test
+    public void getAvailabilityStatus_disabledEuiccManager_unsupportedOnDevice() {
+        getShadowEuiccManager().setIsEnabled(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_euiccNotProvisioned_unsupportedOnDevice() {
+        getShadowEuiccManager().setIsEnabled(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_euiccNotProvisioned_developer_available() {
+        getShadowEuiccManager().setIsEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_euiccProvisioned_available() {
+        getShadowEuiccManager().setIsEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    private ShadowEuiccManager getShadowEuiccManager() {
+        return Shadow.extract(mContext.getSystemService(Context.EUICC_SERVICE));
+    }
+
+    private Map<String, String> getSystemServiceMap() {
+        return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/ResetNetworkConfirmFragmentTest.java b/tests/robotests/src/com/android/car/settings/system/ResetNetworkConfirmFragmentTest.java
new file mode 100644
index 0000000..16fdc53
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/ResetNetworkConfirmFragmentTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+import static org.testng.Assert.assertThrows;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.NetworkPolicyManager;
+import android.net.Uri;
+import android.provider.Telephony;
+import android.telephony.SubscriptionManager;
+import android.widget.Button;
+
+import androidx.preference.PreferenceManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowBluetoothAdapter;
+import com.android.car.settings.testutils.ShadowConnectivityManager;
+import com.android.car.settings.testutils.ShadowNetworkPolicyManager;
+import com.android.car.settings.testutils.ShadowRecoverySystem;
+import com.android.car.settings.testutils.ShadowTelephonyManager;
+import com.android.car.settings.testutils.ShadowWifiManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.List;
+import java.util.Map;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {
+        ShadowConnectivityManager.class,
+        ShadowWifiManager.class,
+        ShadowBluetoothAdapter.class,
+        ShadowTelephonyManager.class,
+        ShadowNetworkPolicyManager.class,
+        ShadowRecoverySystem.class
+})
+public class ResetNetworkConfirmFragmentTest {
+
+    private Context mContext;
+    private Button mResetButton;
+    private ContentResolver mContentResolver;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        getSystemServiceMap().put(Context.NETWORK_POLICY_SERVICE,
+                NetworkPolicyManager.class.getName());
+
+        mContext = RuntimeEnvironment.application;
+        mContentResolver = mContext.getContentResolver();
+
+        setEuiccResetCheckbox(false);
+        setNetworkSubscriptionId("");
+
+        BaseTestActivity testActivity = Robolectric.setupActivity(BaseTestActivity.class);
+
+        ResetNetworkConfirmFragment fragment = new ResetNetworkConfirmFragment();
+        testActivity.launchFragment(fragment);
+
+        mResetButton = testActivity.findViewById(R.id.action_button1);
+    }
+
+    @After
+    public void tearDown() {
+        getSystemServiceMap().remove(Context.NETWORK_POLICY_SERVICE);
+        ShadowConnectivityManager.reset();
+        ShadowWifiManager.reset();
+        ShadowBluetoothAdapter.reset();
+        ShadowRecoverySystem.reset();
+        ShadowTelephonyManager.reset();
+        ShadowNetworkPolicyManager.reset();
+        ShadowContentResolver.reset();
+    }
+
+    @Test
+    public void testResetButtonClick_connectivityManagerReset() {
+        mResetButton.performClick();
+        assertThat(ShadowConnectivityManager.verifyFactoryResetCalled(/* numTimes */ 1)).isTrue();
+    }
+
+    @Test
+    public void testResetButtonClick_wifiManagerReset() {
+        mResetButton.performClick();
+        assertThat(ShadowWifiManager.verifyFactoryResetCalled(/* numTimes */ 1)).isTrue();
+    }
+
+    @Test
+    public void testResetButtonClick_bluetoothAdapterReset() {
+        mResetButton.performClick();
+        assertThat(ShadowBluetoothAdapter.verifyFactoryResetCalled(/* numTimes */ 1)).isTrue();
+    }
+
+    @Test
+    public void testResetButtonClick_cleanSmsRawTable() {
+        mResetButton.performClick();
+        assertThat(getUriWithGivenPrefix(shadowOf(mContentResolver).getDeletedUris(),
+                Telephony.Sms.CONTENT_URI)).isNotNull();
+    }
+
+    @Test
+    public void testResetButtonClick_euiccResetEnabled_euiccReset() {
+        setEuiccResetCheckbox(true);
+        mResetButton.performClick();
+        assertThat(ShadowRecoverySystem.verifyWipeEuiccDataCalled(/* numTimes */ 1)).isTrue();
+    }
+
+    @Test
+    public void testResetButtonClick_euiccResetDisabled_euiccNotReset() {
+        setEuiccResetCheckbox(false);
+        mResetButton.performClick();
+        assertThat(ShadowRecoverySystem.verifyWipeEuiccDataCalled(/* numTimes */ 1)).isFalse();
+    }
+
+    @Test
+    public void testResetButtonClick_nonIntegerNetworkSubscriptionId_numberExceptionError() {
+        setNetworkSubscriptionId("abc");
+        assertThrows(NumberFormatException.class, () -> mResetButton.performClick());
+    }
+
+    @Test
+    public void testResetButtonClick_emptyNetworkSubscriptionId_telephonyNotReset() {
+        setNetworkSubscriptionId("");
+        mResetButton.performClick();
+        assertThat(ShadowTelephonyManager.verifyFactoryResetCalled(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID, /* numTimes */ 1)).isTrue();
+    }
+
+    @Test
+    public void testResetButtonClick_validNetworkSubscriptionId_telephonyReset() {
+        setNetworkSubscriptionId("123");
+        mResetButton.performClick();
+        assertThat(ShadowTelephonyManager.verifyFactoryResetCalled(123, /* numTimes */ 1)).isTrue();
+    }
+
+    @Test
+    public void testResetButtonClick_emptyNetworkSubscriptionId_networkManagerNotReset() {
+        setNetworkSubscriptionId("");
+        mResetButton.performClick();
+        assertThat(ShadowNetworkPolicyManager.verifyFactoryResetCalled(null, /* numTimes */
+                1)).isTrue();
+    }
+
+    @Test
+    public void testResetButtonClick_validNetworkSubscriptionId_networkManagerReset() {
+        setNetworkSubscriptionId("123");
+        mResetButton.performClick();
+        assertThat(ShadowNetworkPolicyManager.verifyFactoryResetCalled(
+                ShadowTelephonyManager.SUBSCRIBER_ID, /* numTimes */ 1)).isTrue();
+    }
+
+    @Test
+    public void testResetButtonClick_emptyNetworkSubscriptionId_noRestoreDefaultApn() {
+        setNetworkSubscriptionId("");
+        mResetButton.performClick();
+        Uri uri = getUriWithGivenPrefix(shadowOf(mContentResolver).getDeletedUris(),
+                ResetNetworkConfirmFragment.RESTORE_CARRIERS_URI);
+        assertThat(uri).isNotNull();
+        assertThat(uri.toString().contains("subId/")).isFalse();
+    }
+
+    @Test
+    public void testResetButtonClick_validNetworkSubscriptionId_restoreDefaultApn() {
+        setNetworkSubscriptionId("123");
+        mResetButton.performClick();
+        Uri uri = getUriWithGivenPrefix(shadowOf(mContentResolver).getDeletedUris(),
+                ResetNetworkConfirmFragment.RESTORE_CARRIERS_URI);
+        assertThat(uri).isNotNull();
+        assertThat(uri.toString().contains("subId/123")).isTrue();
+    }
+
+    private Uri getUriWithGivenPrefix(List<Uri> uris, String prefix) {
+        for (Uri uri : uris) {
+            if (uri.toString().startsWith(prefix)) return uri;
+        }
+        return null;
+    }
+
+    private Uri getUriWithGivenPrefix(List<Uri> uris, Uri prefix) {
+        for (Uri uri : uris) {
+            if (uri.isPathPrefixMatch(prefix)) return uri;
+        }
+        return null;
+    }
+
+    private void setEuiccResetCheckbox(boolean isChecked) {
+        PreferenceManager.getDefaultSharedPreferences(mContext).edit().putBoolean(
+                mContext.getString(R.string.pk_reset_esim), isChecked).commit();
+    }
+
+    private void setNetworkSubscriptionId(String id) {
+        PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(
+                mContext.getString(R.string.pk_reset_network_subscription), id).commit();
+    }
+
+    private Map<String, String> getSystemServiceMap() {
+        return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/ResetNetworkEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/ResetNetworkEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..1034a3f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/ResetNetworkEntryPreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static android.os.UserManager.DISALLOW_NETWORK_RESET;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link ResetNetworkEntryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class ResetNetworkEntryPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private ResetNetworkEntryPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        Context context = RuntimeEnvironment.application;
+
+        mController = new PreferenceControllerTestHelper<>(context,
+                ResetNetworkEntryPreferenceController.class,
+                new Preference(context)).getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_nonAdminUser_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_adminUser_available() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_adminUser_restricted_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessUserHasRestriction(
+                DISALLOW_NETWORK_RESET)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/ResetNetworkSubscriptionPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/ResetNetworkSubscriptionPreferenceControllerTest.java
new file mode 100644
index 0000000..0572237
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/ResetNetworkSubscriptionPreferenceControllerTest.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static android.telephony.SubscriptionManager.MIN_SUBSCRIPTION_ID_VALUE;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowSubscriptionManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+/** Unit test for {@link ResetNetworkSubscriptionPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSubscriptionManager.class})
+public class ResetNetworkSubscriptionPreferenceControllerTest {
+
+    private static final int SUBID_1 = MIN_SUBSCRIPTION_ID_VALUE;
+    private static final int SUBID_2 = SUBID_1 + 1;
+    private static final int SUBID_3 = SUBID_2 + 1;
+    private static final int SUBID_4 = SUBID_3 + 1;
+
+    private Context mContext;
+    private ShadowSubscriptionManager mShadowSubscriptionManager;
+    private ListPreference mListPreference;
+    private PreferenceControllerTestHelper<ResetNetworkSubscriptionPreferenceController>
+            mPreferenceControllerHelper;
+    private ResetNetworkSubscriptionPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        // Robolectric doesn't know about the euicc manager, so we must add it ourselves.
+        getSystemServiceMap().put(Context.TELEPHONY_SUBSCRIPTION_SERVICE,
+                SubscriptionManager.class.getName());
+
+        mContext = RuntimeEnvironment.application;
+        mShadowSubscriptionManager = Shadow.extract(
+                mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE));
+
+        mListPreference = new ListPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                ResetNetworkSubscriptionPreferenceController.class, mListPreference);
+        mController = mPreferenceControllerHelper.getController();
+
+        // Default to AVAILABLE status. Tests for this behavior will do their own setup.
+        ShadowPackageManager shadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        shadowPackageManager.setSystemFeature(PackageManager.FEATURE_TELEPHONY, /* supported= */
+                true);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        getSystemServiceMap().remove(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        ShadowSubscriptionManager.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_telephonyAvailable_available() {
+        ShadowPackageManager shadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        shadowPackageManager.setSystemFeature(PackageManager.FEATURE_TELEPHONY, /* supported= */
+                true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_telephonyNotAvailable_unsupportedOnDevice() {
+        ShadowPackageManager shadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        shadowPackageManager.setSystemFeature(PackageManager.FEATURE_TELEPHONY, /* supported= */
+                false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void refreshUi_nullSubscriptions_hidesPreference() {
+        mController.refreshUi();
+
+        assertThat(mListPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_nullSubscriptions_setsValue() {
+        mController.refreshUi();
+
+        assertThat(mListPreference.getValue()).isEqualTo(
+                String.valueOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+    }
+
+    @Test
+    public void refreshUi_noSubscriptions_hidesPreference() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(Collections.emptyList());
+
+        mController.refreshUi();
+
+        assertThat(mListPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_noSubscriptions_setsValue() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(Collections.emptyList());
+
+        mController.refreshUi();
+
+        assertThat(mListPreference.getValue()).isEqualTo(
+                String.valueOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+    }
+
+    @Test
+    public void refreshUi_oneSubscription_hidesPreference() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Collections.singletonList(createSubInfo(SUBID_1, "sub1")));
+
+        mController.refreshUi();
+
+        assertThat(mListPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_oneSubscription_setsValue() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Collections.singletonList(createSubInfo(SUBID_1, "sub1")));
+
+        mController.refreshUi();
+
+        assertThat(mListPreference.getValue()).isEqualTo(String.valueOf(SUBID_1));
+    }
+
+    @Test
+    public void refreshUi_multipleSubscriptions_showsPreference() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(createSubInfo(SUBID_1, "sub1"), createSubInfo(SUBID_2, "sub2")));
+
+        mController.refreshUi();
+
+        assertThat(mListPreference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_multipleSubscriptions_populatesEntries() {
+        String displayName1 = "sub1";
+        String displayName2 = "sub2";
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(createSubInfo(SUBID_1, displayName1),
+                        createSubInfo(SUBID_2, displayName2)));
+
+        mController.refreshUi();
+
+        assertThat(Arrays.asList(mListPreference.getEntries())).containsExactly(displayName1,
+                displayName2);
+        assertThat(Arrays.asList(mListPreference.getEntryValues())).containsExactly(
+                String.valueOf(SUBID_1),
+                String.valueOf(SUBID_2));
+    }
+
+    @Test
+    public void refreshUi_defaultSelection_fourthPriority_system() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(createSubInfo(SUBID_1, "sub1"), createSubInfo(SUBID_2, "sub2"),
+                        createSubInfo(SUBID_3, "sub3"), createSubInfo(SUBID_4, "sub4")));
+
+        ShadowSubscriptionManager.setDefaultSubscriptionId(SUBID_4);
+        mController.refreshUi();
+
+        assertThat(mListPreference.getValue()).isEqualTo(String.valueOf(SUBID_4));
+    }
+
+    @Test
+    public void refreshUi_defaultSelection_thirdPriority_sms() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(createSubInfo(SUBID_1, "sub1"), createSubInfo(SUBID_2, "sub2"),
+                        createSubInfo(SUBID_3, "sub3"), createSubInfo(SUBID_4, "sub4")));
+
+        ShadowSubscriptionManager.setDefaultSubscriptionId(SUBID_4);
+        ShadowSubscriptionManager.setDefaultSmsSubId(SUBID_3);
+        mController.refreshUi();
+
+        assertThat(mListPreference.getValue()).isEqualTo(String.valueOf(SUBID_3));
+    }
+
+    @Test
+    public void refreshUi_defaultSelection_secondPriority_voice() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(createSubInfo(SUBID_1, "sub1"), createSubInfo(SUBID_2, "sub2"),
+                        createSubInfo(SUBID_3, "sub3"), createSubInfo(SUBID_4, "sub4")));
+
+        ShadowSubscriptionManager.setDefaultSubscriptionId(SUBID_4);
+        ShadowSubscriptionManager.setDefaultSmsSubId(SUBID_3);
+        ShadowSubscriptionManager.setDefaultVoiceSubId(SUBID_2);
+        mController.refreshUi();
+
+        assertThat(mListPreference.getValue()).isEqualTo(String.valueOf(SUBID_2));
+    }
+
+    @Test
+    public void refreshUi_defaultSelection_firstPriority_data() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(createSubInfo(SUBID_1, "sub1"), createSubInfo(SUBID_2, "sub2"),
+                        createSubInfo(SUBID_3, "sub3"), createSubInfo(SUBID_4, "sub4")));
+
+        ShadowSubscriptionManager.setDefaultSubscriptionId(SUBID_4);
+        ShadowSubscriptionManager.setDefaultSmsSubId(SUBID_3);
+        ShadowSubscriptionManager.setDefaultVoiceSubId(SUBID_2);
+        ShadowSubscriptionManager.setDefaultDataSubId(SUBID_1);
+        mController.refreshUi();
+
+        assertThat(mListPreference.getValue()).isEqualTo(String.valueOf(SUBID_1));
+    }
+
+    @Test
+    public void refreshUi_title_fourthPriority_subscriptionNetworkIds() {
+        SubscriptionInfo subInfo = createSubInfo(
+                SUBID_1,
+                /* displayName= */ "",
+                /* carrierName= */ "",
+                /* number= */ "");
+        // Multiple subscriptions so that preference is shown / title is set.
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(subInfo, createSubInfo(SUBID_2, "sub2")));
+        ShadowSubscriptionManager.setDefaultDataSubId(SUBID_1);
+
+        mController.refreshUi();
+
+        String title = mListPreference.getTitle().toString();
+        assertThat(title).contains(String.valueOf(subInfo.getMcc()));
+        assertThat(title).contains(String.valueOf(subInfo.getMnc()));
+        assertThat(title).contains(String.valueOf(subInfo.getSimSlotIndex()));
+        assertThat(title).contains(String.valueOf(subInfo.getSubscriptionId()));
+    }
+
+    @Test
+    public void refreshUi_title_thirdPriority_subscriptionCarrierName() {
+        SubscriptionInfo subInfo = createSubInfo(
+                SUBID_1,
+                /* displayName= */ "",
+                "carrierName",
+                /* number= */ "");
+        // Multiple subscriptions so that preference is shown / title is set.
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(subInfo, createSubInfo(SUBID_2, "sub2")));
+        ShadowSubscriptionManager.setDefaultDataSubId(SUBID_1);
+
+        mController.refreshUi();
+
+        assertThat(mListPreference.getTitle()).isEqualTo(subInfo.getCarrierName());
+    }
+
+    @Test
+    public void refreshUi_title_secondPriority_subscriptionNumber() {
+        SubscriptionInfo subInfo = createSubInfo(
+                SUBID_1,
+                /* displayName= */ "",
+                "carrierName",
+                "number");
+        // Multiple subscriptions so that preference is shown / title is set.
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(subInfo, createSubInfo(SUBID_2, "sub2")));
+        ShadowSubscriptionManager.setDefaultDataSubId(SUBID_1);
+
+        mController.refreshUi();
+
+        assertThat(mListPreference.getTitle()).isEqualTo(subInfo.getNumber());
+    }
+
+    @Test
+    public void refreshUi_title_firstPriority_subscriptionDisplayName() {
+        SubscriptionInfo subInfo = createSubInfo(
+                SUBID_1,
+                "displayName",
+                "carrierName",
+                "number");
+        // Multiple subscriptions so that preference is shown / title is set.
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(subInfo, createSubInfo(SUBID_2, "sub2")));
+        ShadowSubscriptionManager.setDefaultDataSubId(SUBID_1);
+
+        mController.refreshUi();
+
+        assertThat(mListPreference.getTitle()).isEqualTo(subInfo.getDisplayName());
+    }
+
+    @Test
+    public void handlePreferenceChanged_updatesTitle() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(createSubInfo(SUBID_1, "sub1"), createSubInfo(SUBID_2, "sub2")));
+        ShadowSubscriptionManager.setDefaultDataSubId(SUBID_1);
+        mController.refreshUi();
+
+        mListPreference.callChangeListener(String.valueOf(SUBID_2));
+
+        assertThat(mListPreference.getTitle()).isEqualTo("sub2");
+    }
+
+    @Test
+    public void handlePreferenceChanged_returnsTrue() {
+        mShadowSubscriptionManager.setActiveSubscriptionInfoList(
+                Arrays.asList(createSubInfo(SUBID_1, "sub1"), createSubInfo(SUBID_2, "sub2")));
+        ShadowSubscriptionManager.setDefaultDataSubId(SUBID_1);
+        mController.refreshUi();
+
+        assertThat(mController.handlePreferenceChanged(mListPreference,
+                String.valueOf(SUBID_2))).isTrue();
+    }
+
+    /** Reduce SubscriptionInfo constructor args to the ones we care about here. */
+    private SubscriptionInfo createSubInfo(int subId, String displayName) {
+        return createSubInfo(subId, displayName, "carrierName", "number");
+    }
+
+    /** Reduce SubscriptionInfo constructor args to the ones we care about here. */
+    private SubscriptionInfo createSubInfo(int subId, String displayName, String carrierName,
+            String number) {
+        // Hidden constructor so resort to mocking.
+        SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
+        when(subscriptionInfo.getSubscriptionId()).thenReturn(subId);
+        when(subscriptionInfo.getDisplayName()).thenReturn(displayName);
+        when(subscriptionInfo.getCarrierName()).thenReturn(carrierName);
+        when(subscriptionInfo.getNumber()).thenReturn(number);
+        when(subscriptionInfo.getSimSlotIndex()).thenReturn(111);
+        when(subscriptionInfo.getMcc()).thenReturn(222);
+        when(subscriptionInfo.getMnc()).thenReturn(333);
+        return subscriptionInfo;
+    }
+
+    private Map<String, String> getSystemServiceMap() {
+        return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/SystemUpdatePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/SystemUpdatePreferenceControllerTest.java
new file mode 100644
index 0000000..3ed6bfd
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/SystemUpdatePreferenceControllerTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowCarrierConfigManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.List;
+import java.util.Map;
+
+/** Unit test for {@link SystemUpdatePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowCarrierConfigManager.class})
+public class SystemUpdatePreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private Context mContext;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<SystemUpdatePreferenceController> mControllerHelper;
+    private SystemUpdatePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        // Robolectric doesn't know about the carrier service, so we must add it ourselves.
+        getSystemServiceMap().put(Context.CARRIER_CONFIG_SERVICE,
+                CarrierConfigManager.class.getName());
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                SystemUpdatePreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        getSystemServiceMap().remove(Context.CARRIER_CONFIG_SERVICE);
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void getAvailabilityStatus_adminUser_available() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_nonAdminUser_disabledForUser() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void onCreate_setsActivityLabelAsTitle() {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.applicationInfo = applicationInfo;
+        activityInfo.packageName = "some.test.package";
+        activityInfo.name = "SomeActivity";
+
+        String label = "Activity Label";
+        ResolveInfo resolveInfo = new ResolveInfo() {
+            @Override
+            public CharSequence loadLabel(PackageManager pm) {
+                return label;
+            }
+        };
+        resolveInfo.activityInfo = activityInfo;
+
+        Intent intent = new Intent();
+        ShadowPackageManager packageManager = Shadows.shadowOf(mContext.getPackageManager());
+        packageManager.addResolveInfoForIntent(intent, resolveInfo);
+        mPreference.setIntent(intent);
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+
+        assertThat(mPreference.getTitle()).isEqualTo(label);
+    }
+
+    @Test
+    public void refreshUi_activityNotFount_hidesPreference() {
+        mPreference.setIntent(new Intent());
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mController.refreshUi();
+
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void preferenceClicked_triggersClientInitiatedAction() {
+        // Arrange
+        String action = "action";
+        String key = "key";
+        String value = "value";
+
+        PersistableBundle config = new PersistableBundle();
+        config.putBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL, true);
+        config.putString(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING, action);
+        config.putString(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING, key);
+        config.putString(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING, value);
+
+        getShadowCarrierConfigManager().setConfigForSubId(
+                SubscriptionManager.getDefaultSubscriptionId(), config);
+
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        // Act
+        mPreference.performClick();
+
+        // Assert
+        List<Intent> broadcasts = ShadowApplication.getInstance().getBroadcastIntents();
+        assertThat(broadcasts).hasSize(1);
+        Intent broadcast = broadcasts.get(0);
+        assertThat(broadcast.getAction()).isEqualTo(action);
+        assertThat(broadcast.getStringExtra(key)).isEqualTo(value);
+    }
+
+    @Test
+    public void preferenceClicked_handledReturnsFalse() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        assertThat(mPreference.getOnPreferenceClickListener().onPreferenceClick(
+                mPreference)).isFalse();
+    }
+
+    private ShadowCarrierConfigManager getShadowCarrierConfigManager() {
+        return Shadow.extract(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE));
+    }
+
+    private Map<String, String> getSystemServiceMap() {
+        return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/WifiMacAddressPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/WifiMacAddressPreferenceControllerTest.java
new file mode 100644
index 0000000..512aa42
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/WifiMacAddressPreferenceControllerTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+
+/** Unit test for {@link WifiMacAddressPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiMacAddressPreferenceControllerTest {
+
+    private static final String MAC_ADDRESS = "mac address";
+
+    private Preference mPreference;
+    private Context mContext;
+
+    @Mock
+    private WifiManager mMockWifiManager;
+    @Mock
+    private WifiInfo mMockWifiInfo;
+    @Mock
+    private Context mMockContext;
+    private PreferenceControllerTestHelper<WifiMacAddressPreferenceController>
+            mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+
+        // Construct controller.
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+            WifiMacAddressPreferenceController.class, mPreference);
+
+        when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+        when(mMockWifiManager.getConnectionInfo()).thenReturn(mMockWifiInfo);
+        mControllerHelper.getController().init(mMockContext);
+    }
+
+    @Test
+    public void getAvailabilityStatus_wifiAvailable_available() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ true);
+
+        assertThat(mControllerHelper.getController().getAvailabilityStatus())
+                .isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_wifiNotAvailable_unsupportedOnDevice() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ false);
+
+        assertThat(mControllerHelper.getController().getAvailabilityStatus())
+                .isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getSummary_shouldHaveMacAddress() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, true);
+
+        when(mMockWifiInfo.getMacAddress()).thenReturn(MAC_ADDRESS);
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreference.getSummary()).isEqualTo(MAC_ADDRESS);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/system/legal/LegalPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/system/legal/LegalPreferenceControllerTest.java
new file mode 100644
index 0000000..a01e6ef
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/system/legal/LegalPreferenceControllerTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.system.legal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowPackageManager;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/** Unit test for {@link LegalPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class LegalPreferenceControllerTest {
+    private static class TestLegalPreferenceControllerTest extends
+            LegalPreferenceController {
+
+        private static final Intent INTENT = new Intent("test_intent");
+
+        TestLegalPreferenceControllerTest(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return INTENT;
+        }
+    }
+
+    private static final String TEST_LABEL = "test_label";
+    private Context mContext;
+    private PreferenceControllerTestHelper<TestLegalPreferenceControllerTest> mControllerHelper;
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TestLegalPreferenceControllerTest.class, mPreference);
+    }
+
+    @Test
+    public void refreshUi_intentResolvesToActivity_isVisible() {
+        Intent intent = mControllerHelper.getController().getIntent();
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = "some.test.package";
+        activityInfo.name = "SomeActivity";
+        activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        ResolveInfo resolveInfo = new ResolveInfo() {
+            @Override
+            public CharSequence loadLabel(PackageManager pm) {
+                return TEST_LABEL;
+            }
+        };
+        resolveInfo.activityInfo = activityInfo;
+        List<ResolveInfo> list = new LinkedList();
+        list.add(resolveInfo);
+
+        ShadowPackageManager packageManager = Shadows.shadowOf(mContext.getPackageManager());
+        packageManager.addResolveInfoForIntent(intent, list);
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_intentResolvesToActivity_updatesTitle() {
+        Intent intent = mControllerHelper.getController().getIntent();
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = "some.test.package";
+        activityInfo.name = "SomeActivity";
+        activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        ResolveInfo resolveInfo = new ResolveInfo() {
+            @Override
+            public CharSequence loadLabel(PackageManager pm) {
+                return TEST_LABEL;
+            }
+        };
+        resolveInfo.activityInfo = activityInfo;
+        List<ResolveInfo> list = new LinkedList();
+        list.add(resolveInfo);
+
+        ShadowPackageManager packageManager = Shadows.shadowOf(mContext.getPackageManager());
+        packageManager.addResolveInfoForIntent(intent, list);
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreference.getTitle()).isEqualTo(TEST_LABEL);
+    }
+
+    @Test
+    public void refreshUi_intentResolvesToActivity_updatesIntentToSpecificActivity() {
+        Intent intent = mControllerHelper.getController().getIntent();
+
+        String packageName = "com.android.car.settings.testutils";
+        String activityName = "BaseTestActivity";
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = packageName;
+        activityInfo.name = activityName;
+        activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        ResolveInfo resolveInfo = new ResolveInfo() {
+            @Override
+            public CharSequence loadLabel(PackageManager pm) {
+                return TEST_LABEL;
+            }
+        };
+        resolveInfo.activityInfo = activityInfo;
+        List<ResolveInfo> list = new LinkedList();
+        list.add(resolveInfo);
+
+        ShadowPackageManager packageManager = Shadows.shadowOf(mContext.getPackageManager());
+        packageManager.addResolveInfoForIntent(intent, list);
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreference.getIntent().getComponent().flattenToString()).isEqualTo(
+                packageName + "/" + activityName);
+    }
+
+    @Test
+    public void refreshUi_intentResolvesToNull_isNotVisible() {
+        ShadowPackageManager packageManager = Shadows.shadowOf(mContext.getPackageManager());
+
+        packageManager.addResolveInfoForIntent(mControllerHelper.getController().getIntent(),
+                Collections.emptyList());
+
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        mControllerHelper.getController().refreshUi();
+
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/BaseTestActivity.java b/tests/robotests/src/com/android/car/settings/testutils/BaseTestActivity.java
index 5f5eb15..5fff6f9 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/BaseTestActivity.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/BaseTestActivity.java
@@ -17,28 +17,34 @@
 package com.android.car.settings.testutils;
 
 import android.car.drivingstate.CarUxRestrictions;
+import android.content.Intent;
+import android.content.IntentSender;
 import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.BaseFragment;
+import com.android.car.settings.common.ActivityResultCallback;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.UxRestrictionsProvider;
 
 /**
- * Test activity that extends {@link AppCompatActivity}.
- * Used for testing {@code BaseFragment} instances.
+ * Test activity used for testing {@code BaseFragment} instances.
  */
-public class BaseTestActivity extends AppCompatActivity implements
-        BaseFragment.FragmentController,
-        BaseFragment.UXRestrictionsProvider {
+public class BaseTestActivity extends FragmentActivity implements FragmentController,
+        UxRestrictionsProvider {
+
     private boolean mOnBackPressedFlag;
+    private CarUxRestrictions mRestrictionInfo = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+            CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.app_compat_activity);
-        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
-        setSupportActionBar(toolbar);
+        setContentView(R.layout.car_setting_activity);
     }
 
     /**
@@ -47,34 +53,61 @@
      * @param fragment Fragment to add to activity.
      */
     @Override
-    public void launchFragment(BaseFragment fragment) {
+    public void launchFragment(Fragment fragment) {
+        if (fragment instanceof DialogFragment) {
+            throw new IllegalArgumentException(
+                    "cannot launch dialogs with launchFragment() - use showDialog() instead");
+        }
+        String tag = Integer.toString(getSupportFragmentManager().getBackStackEntryCount());
         getSupportFragmentManager()
                 .beginTransaction()
-                .replace(R.id.fragment_container, fragment)
+                .replace(R.id.fragment_container, fragment, tag)
                 .addToBackStack(null)
                 .commit();
     }
 
     @Override
-    public void showDOBlockingMessage() {
+    public void showBlockingMessage() {
         // no-op
     }
 
     @Override
-    public CarUxRestrictions getCarUxRestrictions() {
-        return new CarUxRestrictions.Builder(
-                /* reqOpt= */ true,
-                CarUxRestrictions.UX_RESTRICTIONS_BASELINE,
-                /* timestamp= */ 0
-        ).build();
+    public void showDialog(DialogFragment dialogFragment, @Nullable String tag) {
+        dialogFragment.show(getSupportFragmentManager(), tag);
     }
 
-    public void reattachFragment(BaseFragment fragment) {
-        getSupportFragmentManager()
-                .beginTransaction()
-                .detach(fragment)
-                .attach(fragment)
-                .commit();
+    @Override
+    @Nullable
+    public DialogFragment findDialogByTag(String tag) {
+        Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
+        if (fragment instanceof DialogFragment) {
+            return (DialogFragment) fragment;
+        }
+        return null;
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode,
+            ActivityResultCallback callback) {
+        throw new UnsupportedOperationException(
+                "Unimplemented for activities that implement FragmentController");
+    }
+
+    @Override
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, Bundle options,
+            ActivityResultCallback callback) {
+        throw new UnsupportedOperationException(
+                "Unimplemented for activities that implement FragmentController");
+    }
+
+    @Override
+    public CarUxRestrictions getCarUxRestrictions() {
+        return mRestrictionInfo;
+    }
+
+    public void setCarUxRestrictions(CarUxRestrictions restrictionInfo) {
+        mRestrictionInfo = restrictionInfo;
     }
 
     /**
@@ -103,6 +136,6 @@
 
     @Override
     public void goBack() {
-
+        getSupportFragmentManager().popBackStackImmediate();
     }
 }
diff --git a/tests/robotests/src/com/android/car/settings/testutils/DialogTestUtils.java b/tests/robotests/src/com/android/car/settings/testutils/DialogTestUtils.java
new file mode 100644
index 0000000..303b607
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/DialogTestUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.fragment.app.DialogFragment;
+
+/**
+ * Helper methods for DialogFragment testing.
+ */
+public class DialogTestUtils {
+    private DialogTestUtils() {
+    }
+
+    /**
+     * Invokes onClick on the dialog's positive button.
+     */
+    public static void clickPositiveButton(DialogFragment dialogFragment) {
+        Button positiveButton = dialogFragment.getDialog().getWindow().findViewById(
+                com.android.internal.R.id.button1);
+        positiveButton.callOnClick();
+    }
+
+    /**
+     * Invokes onClick on the dialog's negative button.
+     */
+    public static void clickNegativeButton(DialogFragment dialogFragment) {
+        Button negativeButton = dialogFragment.getDialog().getWindow().findViewById(
+                com.android.internal.R.id.button2);
+        negativeButton.callOnClick();
+    }
+
+    /**
+     * Gets dialog's title.
+     */
+    public static String getTitle(DialogFragment dialogFragment) {
+        TextView titleView = dialogFragment.getDialog().getWindow().findViewById(
+                com.android.internal.R.id.alertTitle);
+        return titleView.getText().toString();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/DummyFragment.java b/tests/robotests/src/com/android/car/settings/testutils/DummyFragment.java
new file mode 100644
index 0000000..dc6cfba
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/DummyFragment.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Empty Fragment.
+ */
+public class DummyFragment extends SettingsFragment {
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.settings_fragment;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/FragmentController.java b/tests/robotests/src/com/android/car/settings/testutils/FragmentController.java
new file mode 100644
index 0000000..a3b28f5
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/FragmentController.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.R;
+
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.android.controller.ComponentController;
+
+/**
+ * Version of FragmentController that can be used for {@link androidx.fragment.app.Fragment} until
+ * upstream support is ready.
+ */
+public class FragmentController<F extends Fragment> extends
+        ComponentController<FragmentController<F>, F> {
+
+    private final F mFragment;
+    private final ActivityController<BaseTestActivity> mActivityController;
+
+    private FragmentController(F fragment) {
+        super(fragment);
+        mFragment = fragment;
+        mActivityController = ActivityController.of(new BaseTestActivity());
+    }
+
+    public static <F extends Fragment> FragmentController<F> of(F fragment) {
+        return new FragmentController<>(fragment);
+    }
+
+    /**
+     * Returns the fragment after attaching it to an activity, calling its onCreate() through
+     * onResume() lifecycle methods and making it visible.
+     */
+    public F setup() {
+        return create().start().resume().visible().get();
+    }
+
+    /**
+     * Creates the activity with {@link Bundle} and adds the fragment to it.
+     */
+    public FragmentController<F> create(final Bundle bundle) {
+        shadowMainLooper.runPaused(
+                () -> mActivityController
+                        .create(bundle)
+                        .get()
+                        .getSupportFragmentManager()
+                        .beginTransaction()
+                        .add(R.id.fragment_container, mFragment)
+                        .commitNow());
+        return this;
+    }
+
+    @Override
+    public FragmentController<F> create() {
+        return create(null);
+    }
+
+    @Override
+    public FragmentController<F> destroy() {
+        shadowMainLooper.runPaused(mActivityController::destroy);
+        return this;
+    }
+
+    public FragmentController<F> start() {
+        shadowMainLooper.runPaused(mActivityController::start);
+        return this;
+    }
+
+    public FragmentController<F> resume() {
+        shadowMainLooper.runPaused(mActivityController::resume);
+        return this;
+    }
+
+    public FragmentController<F> pause() {
+        shadowMainLooper.runPaused(mActivityController::pause);
+        return this;
+    }
+
+    public FragmentController<F> stop() {
+        shadowMainLooper.runPaused(mActivityController::stop);
+        return this;
+    }
+
+    public FragmentController<F> visible() {
+        shadowMainLooper.runPaused(mActivityController::visible);
+        return this;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowAccountManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowAccountManager.java
new file mode 100644
index 0000000..f8bf3b1
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowAccountManager.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Implements(AccountManager.class)
+public class ShadowAccountManager extends org.robolectric.shadows.ShadowAccountManager {
+
+    private final Map<Integer, List<Account>> mAccountsAsUserMap = new ArrayMap<>();
+    private final Map<Integer, List<AuthenticatorDescription>> mAuthenticatorAsUserMap =
+            new ArrayMap<>();
+
+    @Implementation
+    protected Account[] getAccountsAsUser(int userId) {
+        if (mAccountsAsUserMap.containsKey(userId)) {
+            return mAccountsAsUserMap.get(userId).toArray(new Account[]{});
+        }
+        return getAccounts();
+    }
+
+    public void addAccountAsUser(int userId, Account account) {
+        mAccountsAsUserMap.putIfAbsent(userId, new ArrayList<>());
+        mAccountsAsUserMap.get(userId).add(account);
+    }
+
+    @Implementation
+    protected Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) {
+        return getAccountsByType(type);
+    }
+
+    @Implementation
+    protected AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
+        if (mAuthenticatorAsUserMap.containsKey(userId)) {
+            return mAuthenticatorAsUserMap.get(userId).toArray(new AuthenticatorDescription[]{});
+        }
+        return getAuthenticatorTypes();
+    }
+
+    public void addAuthenticatorAsUser(int userId, AuthenticatorDescription authenticator) {
+        mAuthenticatorAsUserMap.putIfAbsent(userId, new ArrayList<>());
+        mAuthenticatorAsUserMap.get(userId).add(authenticator);
+    }
+
+    @Override
+    public void removeAllAccounts() {
+        super.removeAllAccounts();
+        mAccountsAsUserMap.clear();
+        mAuthenticatorAsUserMap.clear();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityManager.java
new file mode 100644
index 0000000..e4f2ca9
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityManager.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.app.ActivityManager;
+import android.content.pm.IPackageDataObserver;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(value = ActivityManager.class, inheritImplementationMethods = true)
+public class ShadowActivityManager extends org.robolectric.shadows.ShadowActivityManager {
+
+    private static boolean sIsApplicationUserDataCleared;
+
+    private String mMostRecentlyStoppedPackage;
+
+    @Resetter
+    public static void reset() {
+        sIsApplicationUserDataCleared = false;
+    }
+
+    @Implementation
+    protected void forceStopPackage(String packageName) {
+        mMostRecentlyStoppedPackage = packageName;
+    }
+
+    @Implementation
+    protected boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) {
+        return sIsApplicationUserDataCleared;
+    }
+
+    public String getMostRecentlyStoppedPackage() {
+        return mMostRecentlyStoppedPackage;
+    }
+
+    public static void setApplicationUserDataCleared(boolean applicationUserDataCleared) {
+        sIsApplicationUserDataCleared = applicationUserDataCleared;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityThread.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityThread.java
new file mode 100644
index 0000000..5cd1ac6
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityThread.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.app.ActivityThread;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.lang.reflect.Proxy;
+
+@Implements(ActivityThread.class)
+public class ShadowActivityThread {
+
+    @Implementation
+    protected static IPackageManager getPackageManager() {
+        ClassLoader classLoader = ShadowActivityThread.class.getClassLoader();
+        Class<?> iPackageManagerClass;
+        try {
+            iPackageManagerClass = classLoader.loadClass("android.content.pm.IPackageManager");
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        return (IPackageManager) Proxy.newProxyInstance(
+                classLoader, new Class[]{iPackageManagerClass}, (proxy, method, args) -> {
+                    if (method.getName().equals("getApplicationInfo")) {
+                        String packageName = (String) args[0];
+                        int flags = (Integer) args[1];
+                        try {
+                            return RuntimeEnvironment.application
+                                    .getPackageManager()
+                                    .getApplicationInfo(packageName, flags);
+                        } catch (PackageManager.NameNotFoundException e) {
+                            throw new RemoteException(e.getMessage());
+                        }
+                    } else if (method.getName().equals("getInstalledApplications")) {
+                        int flags = (Integer) args[0];
+                        return new ParceledListSlice<>(RuntimeEnvironment.application
+                                .getPackageManager()
+                                .getInstalledApplications(flags));
+                    }
+                    throw new UnsupportedOperationException("sorry, not supporting " + method
+                            + " yet!");
+                }
+        );
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowAppOpsManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowAppOpsManager.java
new file mode 100644
index 0000000..841af36
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowAppOpsManager.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Table;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@Implements(value = AppOpsManager.class)
+public class ShadowAppOpsManager {
+
+    private static final long OP_TIME = 1400000000L;
+    private static final long REJECT_TIME = 0L;
+    private static final int DURATION = 10;
+    private static final int PROXY_UID = 0;
+    private static final String PROXY_PACKAGE = "";
+
+    private Table<Integer, InternalKey, Integer> mOpToKeyToMode = HashBasedTable.create();
+
+    @Implementation
+    protected void setMode(int code, int uid, String packageName, int mode) {
+        InternalKey key = new InternalKey(uid, packageName);
+        mOpToKeyToMode.put(code, key, mode);
+    }
+
+    /** Convenience method to get the mode directly instead of wrapped in an op list. */
+    public int getMode(int code, int uid, String packageName) {
+        Integer mode = mOpToKeyToMode.get(code, new InternalKey(uid, packageName));
+        return mode == null ? AppOpsManager.opToDefaultMode(code) : mode;
+    }
+
+    @Implementation
+    protected List<PackageOps> getPackagesForOps(int[] ops) {
+        if (ops == null) {
+            return Collections.emptyList();
+        }
+        ImmutableList.Builder<PackageOps> result = new ImmutableList.Builder<>();
+        for (int i = 0; i < ops.length; i++) {
+            int op = ops[i];
+            Map<InternalKey, Integer> keyToModeMap = mOpToKeyToMode.rowMap().get(op);
+            if (keyToModeMap == null) {
+                continue;
+            }
+            for (InternalKey key : keyToModeMap.keySet()) {
+                Integer mode = keyToModeMap.get(key);
+                if (mode == null) {
+                    mode = AppOpsManager.opToDefaultMode(op);
+                }
+                OpEntry opEntry = new OpEntry(op, mode, OP_TIME, REJECT_TIME, DURATION, PROXY_UID,
+                        PROXY_PACKAGE);
+                PackageOps packageOp = new PackageOps(key.mPackageName, key.mUid,
+                        Collections.singletonList(opEntry));
+                result.add(packageOp);
+            }
+        }
+        return result.build();
+    }
+
+    private static class InternalKey {
+        private int mUid;
+        private String mPackageName;
+
+        InternalKey(int uid, String packageName) {
+            mUid = uid;
+            mPackageName = packageName;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof InternalKey) {
+                InternalKey that = (InternalKey) obj;
+                return mUid == that.mUid && mPackageName.equals(that.mPackageName);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mUid, mPackageName);
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowApplicationPackageManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowApplicationPackageManager.java
new file mode 100644
index 0000000..8200852
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowApplicationPackageManager.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+
+import android.annotation.UserIdInt;
+import android.app.ApplicationPackageManager;
+import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Pair;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Shadow of ApplicationPackageManager that allows the getting of content providers per user. */
+@Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true)
+public class ShadowApplicationPackageManager extends
+        org.robolectric.shadows.ShadowApplicationPackageManager {
+
+    private static Resources sResources = null;
+    private static PackageManager sPackageManager;
+
+    private final Map<Integer, String> mUserIdToDefaultBrowserMap = new HashMap<>();
+    private final Map<String, ComponentName> mPkgToDefaultActivityMap = new HashMap<>();
+    private final Map<String, IntentFilter> mPkgToDefaultActivityIntentFilterMap = new HashMap<>();
+    private final Map<IntentFilter, ComponentName> mPreferredActivities = new LinkedHashMap<>();
+    private final Map<Pair<String, Integer>, Integer> mPkgAndUserIdToIntentVerificationStatusMap =
+            new HashMap<>();
+    private List<ResolveInfo> mHomeActivities = Collections.emptyList();
+    private ComponentName mDefaultHomeActivity;
+
+    @Resetter
+    public static void reset() {
+        sResources = null;
+        sPackageManager = null;
+    }
+
+    @Implementation
+    public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
+        return icon;
+    }
+
+    @Override
+    @Implementation
+    public ProviderInfo resolveContentProviderAsUser(String name, int flags,
+            @UserIdInt int userId) {
+        return resolveContentProvider(name, flags);
+    }
+
+    @Implementation
+    public int getPackageUidAsUser(String packageName, int flags, int userId)
+            throws PackageManager.NameNotFoundException {
+        return 0;
+    }
+
+    @Implementation
+    public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer) {
+        sPackageManager.deleteApplicationCacheFiles(packageName, observer);
+    }
+
+    @Implementation
+    public Resources getResourcesForApplication(String appPackageName)
+            throws PackageManager.NameNotFoundException {
+        return sResources;
+    }
+
+    @Implementation
+    public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
+        return getInstalledApplications(flags);
+    }
+
+    @Implementation
+    public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId)
+            throws PackageManager.NameNotFoundException {
+        return getApplicationInfo(packageName, flags);
+    }
+
+    @Implementation
+    public ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
+        outActivities.addAll(mHomeActivities);
+        return mDefaultHomeActivity;
+    }
+
+    @Implementation
+    @Override
+    public void clearPackagePreferredActivities(String packageName) {
+        mPreferredActivities.clear();
+    }
+
+    @Implementation
+    @Override
+    public int getPreferredActivities(List<IntentFilter> outFilters,
+            List<ComponentName> outActivities, String packageName) {
+        for (IntentFilter filter : mPreferredActivities.keySet()) {
+            ComponentName name = mPreferredActivities.get(filter);
+            // If packageName is null, match everything, else filter by packageName.
+            if (packageName == null) {
+                outFilters.add(filter);
+                outActivities.add(name);
+            } else if (name.getPackageName().equals(packageName)) {
+                outFilters.add(filter);
+                outActivities.add(name);
+            }
+        }
+        return 0;
+    }
+
+    @Implementation
+    @Override
+    public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+            ComponentName activity) {
+        mPreferredActivities.put(filter, activity);
+    }
+
+    @Implementation
+    @Override
+    public String getDefaultBrowserPackageNameAsUser(int userId) {
+        return mUserIdToDefaultBrowserMap.getOrDefault(userId, null);
+    }
+
+    @Implementation
+    @Override
+    public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
+        mUserIdToDefaultBrowserMap.put(userId, packageName);
+        return true;
+    }
+
+    @Implementation
+    @Override
+    public int getIntentVerificationStatusAsUser(String packageName, int userId) {
+        Pair<String, Integer> key = new Pair<>(packageName, userId);
+        return mPkgAndUserIdToIntentVerificationStatusMap.getOrDefault(key,
+                INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
+    }
+
+    @Implementation
+    @Override
+    public boolean updateIntentVerificationStatusAsUser(String packageName, int status,
+            int userId) {
+        Pair<String, Integer> key = new Pair<>(packageName, userId);
+        mPkgAndUserIdToIntentVerificationStatusMap.put(key, status);
+        return true;
+    }
+
+    public void setHomeActivities(List<ResolveInfo> homeActivities) {
+        mHomeActivities = homeActivities;
+    }
+
+    public void setDefaultHomeActivity(ComponentName defaultHomeActivity) {
+        mDefaultHomeActivity = defaultHomeActivity;
+    }
+
+    public static void setResources(Resources resources) {
+        sResources = resources;
+    }
+
+    public static void setPackageManager(PackageManager packageManager) {
+        sPackageManager = packageManager;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowApplicationsState.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowApplicationsState.java
new file mode 100644
index 0000000..383a245
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowApplicationsState.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.app.Application;
+
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(ApplicationsState.class)
+public class ShadowApplicationsState {
+
+    private static ApplicationsState sApplicationsState;
+
+    public static void setInstance(ApplicationsState applicationsState) {
+        sApplicationsState = applicationsState;
+    }
+
+    @Resetter
+    public static void reset() {
+        sApplicationsState = null;
+    }
+
+    @Implementation
+    protected static ApplicationsState getInstance(Application app) {
+        return sApplicationsState;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowAutofillServiceInfo.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowAutofillServiceInfo.java
new file mode 100644
index 0000000..6974376
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowAutofillServiceInfo.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.service.autofill.AutofillServiceInfo;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(AutofillServiceInfo.class)
+public class ShadowAutofillServiceInfo {
+
+    private static String sSettingsActivity;
+
+    public void __constructor__(Context context, ServiceInfo si) {
+        // Do nothing when constructed in code.
+    }
+
+    @Resetter
+    public static void reset() {
+        sSettingsActivity = null;
+    }
+
+    @Implementation
+    protected String getSettingsActivity() {
+        return sSettingsActivity;
+    }
+
+    public static void setSettingsActivity(String settingsActivity) {
+        sSettingsActivity = settingsActivity;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowBluetoothAdapter.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowBluetoothAdapter.java
new file mode 100644
index 0000000..5b98aa7
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowBluetoothAdapter.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
+import static android.bluetooth.BluetoothAdapter.STATE_ON;
+
+import android.bluetooth.BluetoothAdapter;
+import android.os.ParcelUuid;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.Collections;
+import java.util.List;
+
+@Implements(value = BluetoothAdapter.class, inheritImplementationMethods = true)
+public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBluetoothAdapter {
+
+    private static int sResetCalledCount = 0;
+    private String mName;
+    private int mScanMode;
+
+    public static boolean verifyFactoryResetCalled(int numTimes) {
+        return sResetCalledCount == numTimes;
+    }
+
+    @Implementation
+    protected boolean factoryReset() {
+        sResetCalledCount++;
+        return true;
+    }
+
+    @Implementation
+    public static synchronized BluetoothAdapter getDefaultAdapter() {
+        return (BluetoothAdapter) ShadowApplication.getInstance().getBluetoothAdapter();
+    }
+
+    @Implementation
+    public ParcelUuid[] getUuids() {
+        return null;
+    }
+
+    @Implementation
+    public String getName() {
+        return mName;
+    }
+
+    @Implementation
+    public boolean setName(String name) {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        mName = name;
+        return true;
+    }
+
+    @Implementation
+    public int getScanMode() {
+        if (getState() != STATE_ON) {
+            return SCAN_MODE_NONE;
+        }
+        return mScanMode;
+    }
+
+    @Implementation
+    public boolean setScanMode(int scanMode) {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        mScanMode = scanMode;
+        return true;
+    }
+
+    @Implementation
+    public List<Integer> getSupportedProfiles() {
+        return Collections.emptyList();
+    }
+
+    @Resetter
+    public static void reset() {
+        sResetCalledCount = 0;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowBluetoothPan.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowBluetoothPan.java
new file mode 100644
index 0000000..6d629e5
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowBluetoothPan.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 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.car.settings.testutils;
+
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(BluetoothPan.class)
+public class ShadowBluetoothPan {
+    @Implementation
+    public void __constructor__(Context context, BluetoothProfile.ServiceListener l) {
+        // Do nothing. Implemented to avoid NullPointerException in BluetoothPan.
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowCar.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowCar.java
index f96d4d2..2aaa1f0 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowCar.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowCar.java
@@ -16,14 +16,19 @@
 
 package com.android.car.settings.testutils;
 
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
 import android.car.Car;
+import android.car.CarNotConnectedException;
 import android.content.Context;
 import android.content.ServiceConnection;
 
+import org.mockito.stubbing.Answer;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
 
 /**
  * Shadow class for {@link Car}. Components in car support library expects
@@ -32,8 +37,63 @@
 @Implements(Car.class)
 public class ShadowCar {
 
+    private static Car sMockCar = mock(Car.class);
+    private static boolean sIsConnected;
+    private static String sServiceName;
+    private static Object sCarManager;
+
+    /**
+     * Returns a mocked version of a {@link Car} object.
+     */
     @Implementation
-    public static Car createCar(Context context, ServiceConnection serviceConnection) {
-        return mock(Car.class);
+    protected static Car createCar(Context context, ServiceConnection serviceConnection) {
+        if (serviceConnection != null) {
+            doAnswer((Answer<Void>) invocation -> {
+                serviceConnection.onServiceConnected(null, null);
+                return null;
+            }).when(sMockCar).connect();
+            doAnswer((Answer<Void>) invocation -> {
+                serviceConnection.onServiceDisconnected(null);
+                return null;
+            }).when(sMockCar).disconnect();
+        }
+        doReturn(sIsConnected).when(sMockCar).isConnected();
+        if (sServiceName != null) {
+            try {
+                doReturn(sCarManager).when(sMockCar).getCarManager(sServiceName);
+            } catch (CarNotConnectedException e) {
+                // do nothing, have to do this because compiler doesn't understand mock can't throw
+                // exception.
+            }
+        }
+        return sMockCar;
+    }
+
+    /**
+     * Sets the manager returned by {@link Car#getCarManager(String)}.
+     *
+     * @param serviceName the name for the service request that should return this car manager.
+     * @param carManager  the object returned by a call with this service.
+     */
+    public static void setCarManager(String serviceName, Object carManager) {
+        sServiceName = serviceName;
+        sCarManager = carManager;
+        try {
+            doReturn(carManager).when(sMockCar).getCarManager(serviceName);
+        } catch (CarNotConnectedException e) {
+            // do nothing, have to do this because compiler doesn't understand mock can't throw e.
+        }
+    }
+
+    /**
+     * Resets the shadow state, note this will not remove stubbed behavior on references to older
+     * calls to {@link #createCar(Context, ServiceConnection)}.
+     */
+    @Resetter
+    public static void reset() {
+        sMockCar = mock(Car.class);
+        sServiceName = null;
+        sCarManager = null;
+        sIsConnected = false;
     }
 }
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUserManagerHelper.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUserManagerHelper.java
index 842a19c..e0791cb 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUserManagerHelper.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUserManagerHelper.java
@@ -16,18 +16,26 @@
 
 package com.android.car.settings.testutils;
 
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper.OnUsersUpdateListener;
 import android.content.pm.UserInfo;
 
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Shadow for {@link CarUserManagerHelper}
  */
 @Implements(CarUserManagerHelper.class)
 public class ShadowCarUserManagerHelper {
+    // First Map keys from user id to map of restrictions. Second Map keys from restriction id to
+    // bool.
+    private static Map<Integer, Map<String, Boolean>> sUserRestrictionMap = new HashMap<>();
     private static CarUserManagerHelper sMockInstance;
 
     public static void setMockInstance(CarUserManagerHelper userManagerHelper) {
@@ -37,10 +45,138 @@
     @Resetter
     public static void reset() {
         sMockInstance = null;
+        sUserRestrictionMap.clear();
     }
 
     @Implementation
-    public void setUserName(UserInfo user, String name) {
+    protected void setUserName(UserInfo user, String name) {
         sMockInstance.setUserName(user, name);
     }
+
+    @Implementation
+    protected UserInfo getCurrentProcessUserInfo() {
+        return sMockInstance.getCurrentProcessUserInfo();
+    }
+
+    @Implementation
+    protected UserInfo getCurrentForegroundUserInfo() {
+        return sMockInstance.getCurrentForegroundUserInfo();
+    }
+
+    @Implementation
+    protected int getCurrentProcessUserId() {
+        return sMockInstance.getCurrentProcessUserId();
+    }
+
+    @Implementation
+    protected boolean isCurrentProcessUser(UserInfo userInfo) {
+        return sMockInstance.isCurrentProcessUser(userInfo);
+    }
+
+    @Implementation
+    protected List<UserInfo> getAllSwitchableUsers() {
+        return sMockInstance.getAllSwitchableUsers();
+    }
+
+    @Implementation
+    protected List<UserInfo> getAllUsers() {
+        return sMockInstance.getAllUsers();
+    }
+
+    @Implementation
+    public List<UserInfo> getAllPersistentUsers() {
+        return sMockInstance.getAllPersistentUsers();
+    }
+
+    @Implementation
+    public UserInfo createNewNonAdminUser(String userName) {
+        return sMockInstance.createNewNonAdminUser(userName);
+    }
+
+    @Implementation
+    protected void registerOnUsersUpdateListener(OnUsersUpdateListener listener) {
+        sMockInstance.registerOnUsersUpdateListener(listener);
+    }
+
+    @Implementation
+    protected void unregisterOnUsersUpdateListener(OnUsersUpdateListener listener) {
+        sMockInstance.unregisterOnUsersUpdateListener(listener);
+    }
+
+    @Implementation
+    protected boolean isUserLimitReached() {
+        return sMockInstance.isUserLimitReached();
+    }
+
+    @Implementation
+    protected boolean canCurrentProcessModifyAccounts() {
+        return sMockInstance.canCurrentProcessModifyAccounts();
+    }
+
+    @Implementation
+    protected boolean canCurrentProcessAddUsers() {
+        return sMockInstance.canCurrentProcessAddUsers();
+    }
+
+    @Implementation
+    protected int getMaxSupportedRealUsers() {
+        return sMockInstance.getMaxSupportedRealUsers();
+    }
+
+    @Implementation
+    protected boolean canCurrentProcessRemoveUsers() {
+        return sMockInstance.canCurrentProcessRemoveUsers();
+    }
+
+    @Implementation
+    protected boolean canUserBeRemoved(UserInfo userInfo) {
+        return sMockInstance.canUserBeRemoved(userInfo);
+    }
+
+    @Implementation
+    protected void grantAdminPermissions(UserInfo user) {
+        sMockInstance.grantAdminPermissions(user);
+    }
+
+    @Implementation
+    protected boolean isCurrentProcessDemoUser() {
+        return sMockInstance.isCurrentProcessDemoUser();
+    }
+
+    @Implementation
+    protected boolean isCurrentProcessAdminUser() {
+        return sMockInstance.isCurrentProcessAdminUser();
+    }
+
+    @Implementation
+    protected boolean isCurrentProcessGuestUser() {
+        return sMockInstance.isCurrentProcessGuestUser();
+    }
+
+    @Implementation
+    protected boolean isCurrentProcessUserHasRestriction(String restriction) {
+        return sMockInstance.isCurrentProcessUserHasRestriction(restriction);
+    }
+
+    @Implementation
+    protected boolean removeUser(UserInfo userInfo, String guestUserName) {
+        return sMockInstance.removeUser(userInfo, guestUserName);
+    }
+
+    @Implementation
+    public void setUserRestriction(UserInfo userInfo, String restriction, boolean enable) {
+        Map<String, Boolean> permissionsMap = sUserRestrictionMap.getOrDefault(userInfo.id,
+                new HashMap<>());
+        permissionsMap.put(restriction, enable);
+        sUserRestrictionMap.put(userInfo.id, permissionsMap);
+    }
+
+    @Implementation
+    public boolean hasUserRestriction(String restriction, UserInfo userInfo) {
+        // False by default, if nothing was added to our map,
+        if (!sUserRestrictionMap.containsKey(userInfo.id)) {
+            return false;
+        }
+        return sUserRestrictionMap.get(userInfo.id).getOrDefault(restriction, false);
+    }
 }
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowCarWifiManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarWifiManager.java
new file mode 100644
index 0000000..e07116a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarWifiManager.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import com.android.car.settings.wifi.CarWifiManager;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.List;
+
+/** TODO: Refactor all methods to run without relying on sInstance. */
+@Implements(CarWifiManager.class)
+public class ShadowCarWifiManager {
+
+    public static final int STATE_UNKNOWN = -1;
+    public static final int STATE_STARTED = 0;
+    public static final int STATE_STOPPED = 1;
+    public static final int STATE_DESTROYED = 2;
+
+    private static CarWifiManager sInstance;
+    private static int sCurrentState = STATE_UNKNOWN;
+    private static WifiConfiguration sWifiConfiguration = new WifiConfiguration();
+    private static boolean sIsDualModeSupported = true;
+    private static boolean sIsDualBandSupported = true;
+
+    public static void setInstance(CarWifiManager wifiManager) {
+        sInstance = wifiManager;
+    }
+
+    @Resetter
+    public static void reset() {
+        sInstance = null;
+        sWifiConfiguration = new WifiConfiguration();
+        sCurrentState = STATE_UNKNOWN;
+        sIsDualModeSupported = true;
+        sIsDualBandSupported = true;
+    }
+
+    @Implementation
+    public void __constructor__(Context context) {
+    }
+
+    @Implementation
+    public void start() {
+        if (sInstance != null) {
+            sInstance.start();
+        }
+        sCurrentState = STATE_STARTED;
+    }
+
+    @Implementation
+    public void stop() {
+        if (sInstance != null) {
+            sInstance.stop();
+        }
+        sCurrentState = STATE_STOPPED;
+    }
+
+    @Implementation
+    public void destroy() {
+        if (sInstance != null) {
+            sInstance.destroy();
+        }
+        sCurrentState = STATE_DESTROYED;
+    }
+
+    @Implementation
+    public void setWifiApConfig(WifiConfiguration config) {
+        sWifiConfiguration = config;
+    }
+
+    @Implementation
+    public WifiConfiguration getWifiApConfig() {
+        return sWifiConfiguration;
+    }
+
+    @Implementation
+    public boolean setWifiEnabled(boolean enabled) {
+        return sInstance.setWifiEnabled(enabled);
+    }
+
+    @Implementation
+    public boolean isWifiEnabled() {
+        return sInstance.isWifiEnabled();
+    }
+
+    @Implementation
+    public boolean isWifiApEnabled() {
+        return sInstance.isWifiApEnabled();
+    }
+
+    @Implementation
+    public List<AccessPoint> getAllAccessPoints() {
+        return sInstance.getAllAccessPoints();
+    }
+
+    @Implementation
+    public List<AccessPoint> getSavedAccessPoints() {
+        return sInstance.getSavedAccessPoints();
+    }
+
+    @Implementation
+    public void connectToPublicWifi(AccessPoint accessPoint, WifiManager.ActionListener listener) {
+        sInstance.connectToPublicWifi(accessPoint, listener);
+    }
+
+    @Implementation
+    protected void connectToSavedWifi(AccessPoint accessPoint,
+            WifiManager.ActionListener listener) {
+        sInstance.connectToSavedWifi(accessPoint, listener);
+    }
+
+    @Implementation
+    protected boolean isDualModeSupported() {
+        return sIsDualModeSupported;
+    }
+
+    @Implementation
+    protected String getCountryCode() {
+        return "1";
+    }
+
+    @Implementation
+    protected boolean isDualBandSupported() {
+        return sIsDualBandSupported;
+    }
+
+    public static void setIsDualModeSupported(boolean supported) {
+        sIsDualModeSupported = supported;
+    }
+
+    public static void setIsDualBandSupported(boolean supported) {
+        sIsDualBandSupported = supported;
+    }
+
+    public static int getCurrentState() {
+        return sCurrentState;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowCarrierConfigManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarrierConfigManager.java
new file mode 100644
index 0000000..58cc473
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarrierConfigManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.util.SparseArray;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(CarrierConfigManager.class)
+public class ShadowCarrierConfigManager {
+
+    private SparseArray<PersistableBundle> mBundles = new SparseArray<>();
+
+    @Implementation
+    protected PersistableBundle getConfigForSubId(int subId) {
+        return mBundles.get(subId);
+    }
+
+    public void setConfigForSubId(int subId, PersistableBundle config) {
+        mBundles.put(subId, config);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowConnectivityManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowConnectivityManager.java
new file mode 100644
index 0000000..a6d1102
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowConnectivityManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import static org.mockito.Mockito.mock;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.os.Handler;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Implements(value = ConnectivityManager.class, inheritImplementationMethods = true)
+public class ShadowConnectivityManager extends org.robolectric.shadows.ShadowConnectivityManager {
+
+    private static int sResetCalledCount = 0;
+
+    private final Map<Network, NetworkCapabilities> mCapabilitiesMap = new HashMap<>();
+
+    private int mStartTetheringCalledCount = 0;
+    private int mStopTetheringCalledCount = 0;
+    private int mTetheringType;
+
+    public static boolean verifyFactoryResetCalled(int numTimes) {
+        return sResetCalledCount == numTimes;
+    }
+
+    public boolean verifyStartTetheringCalled(int numTimes) {
+        return mStartTetheringCalledCount == numTimes;
+    }
+
+    public boolean verifyStopTetheringCalled(int numTimes) {
+        return mStopTetheringCalledCount == numTimes;
+    }
+
+    public int getTetheringType() {
+        return mTetheringType;
+    }
+
+    public void addNetworkCapabilities(Network network, NetworkCapabilities capabilities) {
+        super.addNetwork(network, mock(NetworkInfo.class));
+        mCapabilitiesMap.put(network, capabilities);
+    }
+
+    @Implementation
+    public NetworkCapabilities getNetworkCapabilities(Network network) {
+        return mCapabilitiesMap.get(network);
+    }
+
+    @Implementation
+    public void startTethering(int type, boolean showProvisioningUi,
+            final ConnectivityManager.OnStartTetheringCallback callback, Handler handler) {
+        mTetheringType = type;
+        mStartTetheringCalledCount++;
+    }
+
+    @Implementation
+    public void stopTethering(int type) {
+        mTetheringType = type;
+        mStopTetheringCalledCount++;
+    }
+
+    @Implementation
+    protected void factoryReset() {
+        sResetCalledCount++;
+    }
+
+    @Resetter
+    public static void reset() {
+        sResetCalledCount = 0;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowContentResolver.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowContentResolver.java
new file mode 100644
index 0000000..e9359ec
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowContentResolver.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.accounts.Account;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.content.SyncAdapterType;
+import android.content.SyncInfo;
+import android.content.SyncStatusInfo;
+import android.content.SyncStatusObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Derived from {@link com.android.settings.testutils.shadow.ShadowContentResolver}
+ *
+ * <p>Needed for many account-related tests because the default ShadowContentResolver does not
+ * include an implementation of getSyncAdapterTypesAsUser, which is used by {@link
+ * com.android.settingslib.accounts.AuthenticatorHelper#buildAccountTypeToAuthoritiesMap}.
+ */
+@Implements(ContentResolver.class)
+public class ShadowContentResolver extends org.robolectric.shadows.ShadowContentResolver {
+    private static final int SYNCABLE = 1;
+
+    private static SyncAdapterType[] sSyncAdapterTypes = new SyncAdapterType[0];
+    private static Map<String, Integer> sSyncable = new HashMap<>();
+    private static Map<String, Boolean> sSyncAutomatically = new HashMap<>();
+    private static Map<Integer, Boolean> sMasterSyncAutomatically = new HashMap<>();
+    private static Map<String, SyncStatusInfo> sSyncStatus = new HashMap<>();
+    private static List<SyncInfo> sSyncs = new ArrayList<>();
+    private static SyncListener sSyncListener;
+    private static SyncStatusObserver sStatusObserver;
+
+    @Implementation
+    public final Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+            CancellationSignal cancellationSignal) {
+        return query(uri, projection, /* selection= */ null, /* selectionArgs= */
+                null, /* sortOrder= */ null, cancellationSignal);
+    }
+
+    @Implementation
+    protected static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
+        return sSyncAdapterTypes;
+    }
+
+    @Implementation
+    protected static int getIsSyncableAsUser(Account account, String authority, int userId) {
+        return sSyncable.getOrDefault(authority, SYNCABLE);
+    }
+
+    @Implementation
+    protected static boolean getSyncAutomaticallyAsUser(Account account, String authority,
+            int userId) {
+        return sSyncAutomatically.getOrDefault(authority, true);
+    }
+
+    @Implementation
+    protected static boolean getMasterSyncAutomaticallyAsUser(int userId) {
+        return sMasterSyncAutomatically.getOrDefault(userId, true);
+    }
+
+    @Implementation
+    protected static List<SyncInfo> getCurrentSyncsAsUser(@UserIdInt int userId) {
+        return sSyncs;
+    }
+
+    @Implementation
+    protected static SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+            @UserIdInt int userId) {
+        return sSyncStatus.get(authority);
+    }
+
+    public static void setSyncAdapterTypes(SyncAdapterType[] syncAdapterTypes) {
+        sSyncAdapterTypes = syncAdapterTypes;
+    }
+
+    @Implementation
+    public static void setIsSyncable(Account account, String authority, int syncable) {
+        sSyncable.put(authority, syncable);
+    }
+
+    @Implementation
+    protected static void setSyncAutomaticallyAsUser(Account account, String authority,
+            boolean sync, @UserIdInt int userId) {
+        sSyncAutomatically.put(authority, sync);
+    }
+
+    @Implementation
+    protected static void setMasterSyncAutomaticallyAsUser(boolean sync, @UserIdInt int userId) {
+        sMasterSyncAutomatically.put(userId, sync);
+    }
+
+    public static void setCurrentSyncs(List<SyncInfo> syncs) {
+        sSyncs = syncs;
+    }
+
+    public static void setSyncStatus(Account account, String authority, SyncStatusInfo status) {
+        sSyncStatus.put(authority, status);
+    }
+
+    @Implementation
+    public static void cancelSyncAsUser(Account account, String authority, @UserIdInt int userId) {
+        if (sSyncListener != null) {
+            sSyncListener.onSyncCanceled(account, authority, userId);
+        }
+    }
+
+    @Implementation
+    public static void requestSyncAsUser(Account account, String authority, @UserIdInt int userId,
+            Bundle extras) {
+        if (sSyncListener != null) {
+            sSyncListener.onSyncRequested(account, authority, userId, extras);
+        }
+    }
+
+    public static void setSyncListener(SyncListener syncListener) {
+        sSyncListener = syncListener;
+    }
+
+    @Implementation
+    protected static Object addStatusChangeListener(int mask, SyncStatusObserver callback) {
+        sStatusObserver = callback;
+        return null;
+    }
+
+    @Implementation
+    protected static void removeStatusChangeListener(Object handle) {
+        sStatusObserver = null;
+    }
+
+    public static SyncStatusObserver getStatusChangeListener() {
+        return sStatusObserver;
+    }
+
+    @Resetter
+    public static void reset() {
+        org.robolectric.shadows.ShadowContentResolver.reset();
+        sSyncable.clear();
+        sSyncAutomatically.clear();
+        sMasterSyncAutomatically.clear();
+        sSyncAdapterTypes = new SyncAdapterType[0];
+        sSyncStatus.clear();
+        sSyncs = new ArrayList<>();
+        sSyncListener = null;
+        sStatusObserver = null;
+    }
+
+    /**
+     * A listener interface that can be used to verify calls to {@link #cancelSyncAsUser} and {@link
+     * #requestSyncAsUser}
+     */
+    public interface SyncListener {
+        void onSyncCanceled(Account account, String authority, @UserIdInt int userId);
+
+        void onSyncRequested(Account account, String authority, @UserIdInt int userId,
+                Bundle extras);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowDataUsageController.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowDataUsageController.java
new file mode 100644
index 0000000..bbbe214
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowDataUsageController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.net.NetworkTemplate;
+
+import com.android.settingslib.net.DataUsageController;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(DataUsageController.class)
+public class ShadowDataUsageController {
+
+    private static DataUsageController sInstance;
+
+    public static void setInstance(DataUsageController dataUsageController) {
+        sInstance = dataUsageController;
+    }
+
+    @Implementation
+    protected DataUsageController.DataUsageInfo getDataUsageInfo(NetworkTemplate template) {
+        return sInstance.getDataUsageInfo(template);
+    }
+
+    @Resetter
+    public static void reset() {
+        sInstance = null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowDefaultDialerManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowDefaultDialerManager.java
new file mode 100644
index 0000000..2abc953
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowDefaultDialerManager.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.Context;
+import android.telecom.DefaultDialerManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(DefaultDialerManager.class)
+public class ShadowDefaultDialerManager {
+
+    private static String sDefaultDialerPackage;
+
+    @Resetter
+    public static void reset() {
+        sDefaultDialerPackage = null;
+    }
+
+    @Implementation
+    protected static String getDefaultDialerApplication(Context context) {
+        return sDefaultDialerPackage;
+    }
+
+    public static void setDefaultDialerApplication(String defaultDialerPackage) {
+        sDefaultDialerPackage = defaultDialerPackage;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowDevicePolicyManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowDevicePolicyManager.java
new file mode 100644
index 0000000..0e4f28c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowDevicePolicyManager.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.util.ArraySet;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.List;
+import java.util.Set;
+
+@Implements(value = DevicePolicyManager.class, inheritImplementationMethods = true)
+public class ShadowDevicePolicyManager extends org.robolectric.shadows.ShadowDevicePolicyManager {
+    @Nullable
+    private List<String> mPermittedInputMethods;
+    private Set<String> mActiveAdminsPackages = new ArraySet<>();
+    private boolean mIsInstallInQueue;
+
+    @Implementation
+    @Nullable
+    public List<String> getPermittedInputMethodsForCurrentUser() {
+        return mPermittedInputMethods;
+    }
+
+    public void setPermittedInputMethodsForCurrentUser(@Nullable List<String> inputMethods) {
+        mPermittedInputMethods = inputMethods;
+    }
+
+    @Implementation
+    protected boolean packageHasActiveAdmins(String packageName) {
+        return mActiveAdminsPackages.contains(packageName);
+    }
+
+    public void setPackageHasActiveAdmins(String packageName, boolean hasActiveAdmins) {
+        if (hasActiveAdmins) {
+            mActiveAdminsPackages.add(packageName);
+        } else {
+            mActiveAdminsPackages.remove(packageName);
+        }
+    }
+
+    @Implementation
+    protected boolean isUninstallInQueue(String packageName) {
+        return mIsInstallInQueue;
+    }
+
+    public void setIsUninstallInQueue(boolean isUninstallInQueue) {
+        mIsInstallInQueue = isUninstallInQueue;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowEuiccManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowEuiccManager.java
new file mode 100644
index 0000000..f4f581f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowEuiccManager.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.telephony.euicc.EuiccManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(EuiccManager.class)
+public class ShadowEuiccManager {
+
+    private boolean mIsEnabled;
+
+    @Implementation
+    public void __constructor__() {
+        // Robolectric instantiates unknown services using the no-arg constructor. Create a shadow
+        // so it doesn't complain about Stub!
+    }
+
+    @Implementation
+    protected boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    public void setIsEnabled(boolean isEnabled) {
+        mIsEnabled = isEnabled;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowINetworkStatsServiceStub.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowINetworkStatsServiceStub.java
new file mode 100644
index 0000000..9de5a1d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowINetworkStatsServiceStub.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.net.INetworkStatsService;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(value = INetworkStatsService.Stub.class)
+public class ShadowINetworkStatsServiceStub {
+
+    private static INetworkStatsService sINetworkStatsService;
+
+    @Resetter
+    public static void reset() {
+        sINetworkStatsService = null;
+    }
+
+    @Implementation
+    public static android.net.INetworkStatsService asInterface(android.os.IBinder obj) {
+        return sINetworkStatsService;
+    }
+
+    public static void setINetworkStatsSession(INetworkStatsService iNetworkStatsService) {
+        sINetworkStatsService = iNetworkStatsService;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowISms.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowISms.java
new file mode 100644
index 0000000..17a5073
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowISms.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.os.IBinder;
+
+import com.android.internal.telephony.ISms;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(value = ISms.Stub.class)
+public class ShadowISms {
+
+    private static ISms sISms;
+
+    @Resetter
+    public static void reset() {
+        sISms = null;
+    }
+
+    public static void setISms(ISms iSms) {
+        sISms = iSms;
+    }
+
+    @Implementation
+    protected static ISms asInterface(IBinder obj) {
+        return sISms;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowIUsbManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowIUsbManager.java
new file mode 100644
index 0000000..b940a2e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowIUsbManager.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.hardware.usb.IUsbManager;
+import android.os.IBinder;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(value = IUsbManager.Stub.class)
+public class ShadowIUsbManager {
+
+    private static IUsbManager sInstance;
+
+    public static void setInstance(IUsbManager instance) {
+        sInstance = instance;
+    }
+
+    @Implementation
+    public static IUsbManager asInterface(IBinder obj) {
+        return sInstance;
+    }
+
+    @Resetter
+    public static void reset() {
+        sInstance = null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowIconDrawableFactory.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowIconDrawableFactory.java
new file mode 100644
index 0000000..29ab22c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowIconDrawableFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.IconDrawableFactory;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(value = IconDrawableFactory.class)
+public class ShadowIconDrawableFactory {
+
+    @Implementation
+    protected Drawable getBadgedIcon(ApplicationInfo appInfo) {
+        return new ColorDrawable(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowInputMethodManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowInputMethodManager.java
new file mode 100644
index 0000000..f6ccbc0
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowInputMethodManager.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.provider.Settings;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.settings.inputmethod.InputMethodUtil;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Implements(value = InputMethodManager.class)
+public class ShadowInputMethodManager extends org.robolectric.shadows.ShadowInputMethodManager {
+    private List<InputMethodInfo> mInputMethodList;
+    private Map<String, InputMethodInfo> mInputMethodMap;
+    private List<InputMethodSubtype> mInputMethodSubtypes;
+
+    public void setEnabledInputMethodList(@Nullable List<InputMethodInfo> inputMethodInfos) {
+        String concatenatedInputMethodIds = createInputMethodIdString(inputMethodInfos.stream().map(
+                imi -> imi.getId()).collect(Collectors.toList()).toArray(
+                new String[inputMethodInfos.size()]));
+        Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS, concatenatedInputMethodIds);
+        addInputMethodInfosToMap(inputMethodInfos);
+    }
+
+    @Implementation
+    protected List<InputMethodInfo> getEnabledInputMethodList() {
+        List<InputMethodInfo> enabledInputMethodList = new ArrayList<>();
+
+        String inputMethodIdString = Settings.Secure.getString(
+                RuntimeEnvironment.application.getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS);
+        if (inputMethodIdString == null || inputMethodIdString.isEmpty()) {
+            return enabledInputMethodList;
+        }
+
+        InputMethodUtil.sInputMethodSplitter.setString(inputMethodIdString);
+        while (InputMethodUtil.sInputMethodSplitter.hasNext()) {
+            enabledInputMethodList.add(mInputMethodMap.get(InputMethodUtil.sInputMethodSplitter
+                    .next()));
+        }
+        return enabledInputMethodList;
+    }
+
+    public void setEnabledInputMethodSubtypeList(List<InputMethodSubtype> list) {
+        mInputMethodSubtypes = list;
+    }
+
+    @Implementation
+    protected List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
+            boolean allowsImplicitlySelectedSubtypes) {
+        return mInputMethodSubtypes;
+    }
+
+    public void setInputMethodList(List<InputMethodInfo> inputMethodInfos) {
+        mInputMethodList = inputMethodInfos;
+        if (inputMethodInfos == null) {
+            return;
+        }
+
+        addInputMethodInfosToMap(inputMethodInfos);
+    }
+
+    @Implementation
+    protected List<InputMethodInfo> getInputMethodList() {
+        return mInputMethodList;
+    }
+
+    private static String createInputMethodIdString(String... ids) {
+        int size = ids == null ? 0 : ids.length;
+
+        if (size == 1) {
+            return ids[0];
+        }
+
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < size; i++) {
+            builder.append(ids[i]);
+            if (i != size - 1) {
+                builder.append(InputMethodUtil.INPUT_METHOD_DELIMITER);
+            }
+        }
+        return builder.toString();
+    }
+
+    private void addInputMethodInfosToMap(List<InputMethodInfo> inputMethodInfos) {
+        if (mInputMethodMap == null || mInputMethodMap.size() == 0) {
+            mInputMethodMap = inputMethodInfos.stream().collect(Collectors.toMap(
+                    InputMethodInfo::getId, imi -> imi));
+            return;
+        }
+
+        inputMethodInfos.forEach(imi -> {
+            mInputMethodMap.put(imi.getId(), imi);
+        });
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowLocalBroadcastManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowLocalBroadcastManager.java
new file mode 100644
index 0000000..54bbe71
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowLocalBroadcastManager.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+@Implements(LocalBroadcastManager.class)
+public class ShadowLocalBroadcastManager {
+    private static List<Intent> sSentBroadcastIntents = new ArrayList<>();
+    private static List<Wrapper> sRegisteredReceivers = new ArrayList<>();
+
+    @Implementation
+    public static LocalBroadcastManager getInstance(final Context context) {
+        return ShadowApplication.getInstance().getSingleton(LocalBroadcastManager.class,
+                () -> ReflectionHelpers.callConstructor(LocalBroadcastManager.class,
+                        ClassParameter.from(Context.class, context)));
+    }
+
+    @Implementation
+    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        sRegisteredReceivers.add(new Wrapper(receiver, filter));
+    }
+
+    @Implementation
+    public void unregisterReceiver(BroadcastReceiver receiver) {
+        Iterator<Wrapper> iterator = sRegisteredReceivers.iterator();
+        while (iterator.hasNext()) {
+            Wrapper wrapper = iterator.next();
+            if (wrapper.getBroadcastReceiver() == receiver) {
+                iterator.remove();
+            }
+        }
+    }
+
+    @Implementation
+    public boolean sendBroadcast(Intent intent) {
+        boolean sent = false;
+        sSentBroadcastIntents.add(intent);
+        List<Wrapper> copy = new ArrayList<>(sRegisteredReceivers);
+        for (Wrapper wrapper : copy) {
+            if (wrapper.getIntentFilter().matchAction(intent.getAction())) {
+                int match = wrapper.getIntentFilter().matchData(intent.getType(),
+                        intent.getScheme(), intent.getData());
+                if (match != IntentFilter.NO_MATCH_DATA && match != IntentFilter.NO_MATCH_TYPE) {
+                    sent = true;
+                    final BroadcastReceiver receiver = wrapper.getBroadcastReceiver();
+                    final Intent broadcastIntent = intent;
+                    Robolectric.getForegroundThreadScheduler().post(
+                            (Runnable) () -> receiver.onReceive(RuntimeEnvironment.application,
+                                    broadcastIntent));
+                }
+            }
+        }
+        return sent;
+    }
+
+    @Resetter
+    public static void reset() {
+        sSentBroadcastIntents.clear();
+        sRegisteredReceivers.clear();
+    }
+
+    public static List<Intent> getSentBroadcastIntents() {
+        return sSentBroadcastIntents;
+    }
+
+    public static List<Wrapper> getRegisteredBroadcastReceivers() {
+        return sRegisteredReceivers;
+    }
+
+    public static class Wrapper {
+        private final BroadcastReceiver mBroadcastReceiver;
+        private final IntentFilter mIntentFilter;
+
+        public Wrapper(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter) {
+            this.mBroadcastReceiver = broadcastReceiver;
+            this.mIntentFilter = intentFilter;
+        }
+
+        public BroadcastReceiver getBroadcastReceiver() {
+            return mBroadcastReceiver;
+        }
+
+        public IntentFilter getIntentFilter() {
+            return mIntentFilter;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowLocalePicker.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowLocalePicker.java
new file mode 100644
index 0000000..ed59177
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowLocalePicker.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import com.android.internal.app.LocalePicker;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.Locale;
+
+@Implements(LocalePicker.class)
+public class ShadowLocalePicker {
+
+    private static boolean sLocaleUpdated = false;
+
+    @Resetter
+    public static void reset() {
+        sLocaleUpdated = false;
+    }
+
+    @Implementation
+    protected static void updateLocale(Locale locale) {
+        sLocaleUpdated = true;
+    }
+
+    public static boolean localeWasUpdated() {
+        return sLocaleUpdated;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowLocaleStore.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowLocaleStore.java
new file mode 100644
index 0000000..a9dc5ce
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowLocaleStore.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.Context;
+
+import com.android.internal.app.LocaleStore;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+@Implements(LocaleStore.class)
+public class ShadowLocaleStore {
+
+    private static Map<LocaleStore.LocaleInfo, Set<LocaleStore.LocaleInfo>>
+            sLocaleInfoRelationships = new HashMap<>();
+
+    public static void addLocaleRelationship(Locale parent, Locale child) {
+        LocaleStore.LocaleInfo parentInfo = LocaleStore.getLocaleInfo(parent);
+        LocaleStore.LocaleInfo childInfo = LocaleStore.getLocaleInfo(child);
+        Set<LocaleStore.LocaleInfo> set = sLocaleInfoRelationships.getOrDefault(parentInfo,
+                new HashSet<>());
+        set.add(childInfo);
+        sLocaleInfoRelationships.put(parentInfo, set);
+    }
+
+    @Resetter
+    public static void reset() {
+        sLocaleInfoRelationships.clear();
+    }
+
+    @Implementation
+    protected static Set<LocaleStore.LocaleInfo> getLevelLocales(Context context,
+            Set<String> ignorables, LocaleStore.LocaleInfo parent, boolean translatedOnly) {
+        if (parent != null) {
+            return sLocaleInfoRelationships.getOrDefault(parent, new HashSet<>());
+        } else {
+            return sLocaleInfoRelationships.keySet();
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowLocationManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowLocationManager.java
new file mode 100644
index 0000000..e92d342
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowLocationManager.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.Intent;
+import android.location.LocationManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(value = LocationManager.class)
+public class ShadowLocationManager {
+
+    @Implementation
+    protected void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
+        int newMode = enabled
+                ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
+                : Settings.Secure.LOCATION_MODE_OFF;
+
+        Settings.Secure.putIntForUser(RuntimeEnvironment.application.getContentResolver(),
+                Settings.Secure.LOCATION_MODE, newMode, userHandle.getIdentifier());
+        RuntimeEnvironment.application.sendBroadcast(new Intent(
+                LocationManager.MODE_CHANGED_ACTION));
+    }
+
+    @Implementation
+    protected boolean isLocationEnabled() {
+        return Settings.Secure.getInt(RuntimeEnvironment.application.getContentResolver(),
+                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF)
+                != Settings.Secure.LOCATION_MODE_OFF;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java
new file mode 100644
index 0000000..8f819c3
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.app.admin.DevicePolicyManager;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shadow for LockPatternUtils.
+ */
+@Implements(LockPatternUtils.class)
+public class ShadowLockPatternUtils {
+
+    private static LockPatternUtils sInstance;
+    private static int sPasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+    private static byte[] sSavedPassword;
+    private static List<LockPatternView.Cell> sSavedPattern;
+
+    public static void setInstance(LockPatternUtils lockPatternUtils) {
+        sInstance = lockPatternUtils;
+    }
+
+    @Resetter
+    public static void reset() {
+        sInstance = null;
+        sPasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+        sSavedPassword = null;
+        sSavedPattern = null;
+    }
+
+    /**
+     * Sets the current password quality that is returned by
+     * {@link LockPatternUtils#getKeyguardStoredPasswordQuality}.
+     */
+    public static void setPasswordQuality(int passwordQuality) {
+        sPasswordQuality = passwordQuality;
+    }
+
+    /**
+     * Returns the password saved by a call to {@link LockPatternUtils#saveLockPassword}.
+     */
+    public static byte[] getSavedPassword() {
+        return sSavedPassword;
+    }
+
+    /**
+     * Returns the pattern saved by a call to {@link LockPatternUtils#saveLockPattern}.
+     */
+    public static List<LockPatternView.Cell> getSavedPattern() {
+        return sSavedPattern;
+    }
+
+    @Implementation
+    protected void clearLock(byte[] savedCredential, int userHandle) {
+        sInstance.clearLock(savedCredential, userHandle);
+    }
+
+    @Implementation
+    public int getKeyguardStoredPasswordQuality(int userHandle) {
+        return sPasswordQuality;
+    }
+
+    @Implementation
+    public void saveLockPassword(byte[] password, byte[] savedPassword, int requestedQuality,
+            int userHandler) {
+        sSavedPassword = password;
+    }
+
+    @Implementation
+    public void saveLockPattern(List<LockPatternView.Cell> pattern, int userId) {
+        sSavedPattern = new ArrayList<>(pattern);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyEditor.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyEditor.java
new file mode 100644
index 0000000..85d16a6
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyEditor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 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.car.settings.testutils;
+
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+
+import com.android.settingslib.NetworkPolicyEditor;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(NetworkPolicyEditor.class)
+public class ShadowNetworkPolicyEditor {
+
+    private static NetworkPolicy sNetworkPolicy;
+
+    @Implementation
+    public NetworkPolicy getPolicy(NetworkTemplate template) {
+        return sNetworkPolicy;
+    }
+
+    public static void setNetworkPolicy(NetworkPolicy networkPolicy) {
+        sNetworkPolicy = networkPolicy;
+    }
+
+    @Resetter
+    public static void reset() {
+        sNetworkPolicy = null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyManager.java
new file mode 100644
index 0000000..3d506d0
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyManager.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.Context;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.util.Pair;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+@Implements(NetworkPolicyManager.class)
+public class ShadowNetworkPolicyManager {
+
+    private static NetworkPolicyManager sNetworkPolicyManager;
+    private static Iterator<Pair<ZonedDateTime, ZonedDateTime>> sCycleIterator;
+    private static Map<String, Integer> sResetCalledForSubscriberCount = new HashMap<>();
+
+    public static boolean verifyFactoryResetCalled(String subscriber, int numTimes) {
+        if (!sResetCalledForSubscriberCount.containsKey(subscriber)) return false;
+        return sResetCalledForSubscriberCount.get(subscriber) == numTimes;
+    }
+
+    @Implementation
+    protected void factoryReset(String subscriber) {
+        sResetCalledForSubscriberCount.put(subscriber,
+                sResetCalledForSubscriberCount.getOrDefault(subscriber, 0) + 1);
+    }
+
+    @Implementation
+    protected int[] getUidsWithPolicy(int policy) {
+        return sNetworkPolicyManager == null ? new int[0] : sNetworkPolicyManager
+                .getUidsWithPolicy(policy);
+    }
+
+    @Implementation
+    protected static Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator(
+            NetworkPolicy policy) {
+        return sCycleIterator;
+    }
+
+    public static void setCycleIterator(
+            Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator) {
+        sCycleIterator = cycleIterator;
+    }
+
+    @Implementation
+    public static NetworkPolicyManager from(Context context) {
+        return sNetworkPolicyManager;
+    }
+
+    public static void setNetworkPolicyManager(NetworkPolicyManager networkPolicyManager) {
+        sNetworkPolicyManager = networkPolicyManager;
+    }
+
+    @Resetter
+    public static void reset() {
+        sResetCalledForSubscriberCount.clear();
+        sCycleIterator = null;
+        sNetworkPolicyManager = null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowNotificationManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowNotificationManager.java
new file mode 100644
index 0000000..3aab74f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowNotificationManager.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.app.AutomaticZenRule;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.Map;
+import java.util.Set;
+
+@Implements(NotificationManager.class)
+public class ShadowNotificationManager extends org.robolectric.shadows.ShadowNotificationManager {
+
+    private Set<String> mNotificationPolicyGrantedPackages = new ArraySet<>();
+    private Set<ComponentName> mNotificationListenerAccessGrantedComponents = new ArraySet<>();
+    private Map<String, AutomaticZenRule> mAutomaticZenRules = new ArrayMap<>();
+    private int mZenRuleIdCounter = 0;
+
+    @Implementation
+    public void setNotificationPolicyAccessGranted(String pkg, boolean granted) {
+        if (granted) {
+            mNotificationPolicyGrantedPackages.add(pkg);
+        } else {
+            mNotificationPolicyGrantedPackages.remove(pkg);
+        }
+    }
+
+    @Implementation
+    protected boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {
+        return mNotificationPolicyGrantedPackages.contains(pkg);
+    }
+
+    @Implementation
+    protected boolean isNotificationListenerAccessGranted(ComponentName listener) {
+        return mNotificationListenerAccessGrantedComponents.contains(listener);
+    }
+
+    @Implementation
+    protected void setNotificationListenerAccessGranted(ComponentName listener, boolean granted) {
+        if (granted) {
+            mNotificationListenerAccessGrantedComponents.add(listener);
+        } else {
+            mNotificationListenerAccessGrantedComponents.remove(listener);
+        }
+    }
+
+    @Implementation
+    protected Map<String, AutomaticZenRule> getAutomaticZenRules() {
+        return mAutomaticZenRules;
+    }
+
+    @Implementation
+    protected String addAutomaticZenRule(AutomaticZenRule automaticZenRule) {
+        String id = String.valueOf(mZenRuleIdCounter++);
+        mAutomaticZenRules.put(id, automaticZenRule);
+        return id;
+    }
+
+    @Implementation
+    protected void removeAutomaticZenRules(String packageName) {
+        mAutomaticZenRules.clear();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowOemLockManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowOemLockManager.java
new file mode 100644
index 0000000..44264d2
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowOemLockManager.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.service.oemlock.OemLockManager;
+
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(OemLockManager.class)
+public class ShadowOemLockManager {
+
+    private boolean mIsOemUnlockAllowed;
+
+    @Implementation
+    protected boolean isOemUnlockAllowed() {
+        return mIsOemUnlockAllowed;
+    }
+
+    public void setIsOemUnlockAllowed(boolean isOemUnlockAllowed) {
+        mIsOemUnlockAllowed = isOemUnlockAllowed;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowPersistentDataBlockManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowPersistentDataBlockManager.java
new file mode 100644
index 0000000..ebe257b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowPersistentDataBlockManager.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.service.persistentdata.PersistentDataBlockManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(PersistentDataBlockManager.class)
+public class ShadowPersistentDataBlockManager {
+
+    private int mWipeCalledCount;
+
+    @Implementation
+    protected void wipe() {
+        mWipeCalledCount++;
+    }
+
+    public int getWipeCalledCount() {
+        return mWipeCalledCount;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowPowerManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowPowerManager.java
new file mode 100644
index 0000000..9433b6e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowPowerManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.os.PowerManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(PowerManager.class)
+public class ShadowPowerManager {
+
+    private static PowerManager sInstance;
+
+    public static void setInstance(PowerManager powerManager) {
+        sInstance = powerManager;
+    }
+
+    @Resetter
+    public static void reset() {
+        sInstance = null;
+    }
+
+    @Implementation
+    protected int getMinimumScreenBrightnessSetting() {
+        return sInstance.getMinimumScreenBrightnessSetting();
+    }
+
+    @Implementation
+    protected int getMaximumScreenBrightnessSetting() {
+        return sInstance.getMaximumScreenBrightnessSetting();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowRecoverySystem.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowRecoverySystem.java
new file mode 100644
index 0000000..90f37d7
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowRecoverySystem.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.Context;
+import android.os.RecoverySystem;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(RecoverySystem.class)
+public class ShadowRecoverySystem {
+
+    private static int sWipeEuiccDataCalledCount = 0;
+
+    public static boolean verifyWipeEuiccDataCalled(int numTimes) {
+        return sWipeEuiccDataCalledCount == numTimes;
+    }
+
+    @Implementation
+    protected static boolean wipeEuiccData(Context context, final String packageName) {
+        sWipeEuiccDataCalledCount++;
+        return true;
+    }
+
+    @Resetter
+    public static void reset() {
+        sWipeEuiccDataCalledCount = 0;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowRestrictedLockUtils.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowRestrictedLockUtils.java
new file mode 100644
index 0000000..7dfe324
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowRestrictedLockUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.Context;
+
+import com.android.settingslib.RestrictedLockUtils;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(RestrictedLockUtils.class)
+public class ShadowRestrictedLockUtils {
+
+    private static RestrictedLockUtils.EnforcedAdmin sEnforcedAdmin;
+    private static boolean sHasBaseUserRestriction;
+
+    @Resetter
+    public static void reset() {
+        sEnforcedAdmin = null;
+        sHasBaseUserRestriction = false;
+    }
+
+    public static void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin enforcedAdmin) {
+        sEnforcedAdmin = enforcedAdmin;
+    }
+
+    public static void setHasBaseUserRestriction(boolean hasBaseUserRestriction) {
+        sHasBaseUserRestriction = hasBaseUserRestriction;
+    }
+
+    public static void sendShowAdminSupportDetailsIntent(Context context,
+            RestrictedLockUtils.EnforcedAdmin admin) {
+        // do nothing
+    }
+
+    @Implementation
+    protected static RestrictedLockUtils.EnforcedAdmin checkIfRestrictionEnforced(Context context,
+            String userRestriction, int userId) {
+        return sEnforcedAdmin;
+    }
+
+    @Implementation
+    protected static boolean hasBaseUserRestriction(Context context,
+            String userRestriction, int userId) {
+        return sHasBaseUserRestriction;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowRingtone.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowRingtone.java
new file mode 100644
index 0000000..4be9cfe
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowRingtone.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.Context;
+import android.media.Ringtone;
+import android.net.Uri;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Implements(Ringtone.class)
+public class ShadowRingtone {
+
+    private static Map<Uri, String> sTitleForGivenUri = new HashMap<>();
+
+    public static void setExpectedTitleForUri(Uri uri, String title) {
+        sTitleForGivenUri.put(uri, title);
+    }
+
+    @Implementation
+    protected static String getTitle(
+            Context context, Uri uri, boolean followSettingsUri, boolean allowRemote) {
+        return sTitleForGivenUri.getOrDefault(uri, null);
+    }
+
+    @Resetter
+    public static void reset() {
+        sTitleForGivenUri.clear();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowRingtoneManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowRingtoneManager.java
new file mode 100644
index 0000000..026ec98
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowRingtoneManager.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.Context;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Implements(RingtoneManager.class)
+public class ShadowRingtoneManager {
+    private static Ringtone sRingtone;
+    private static Map<Integer, Uri> sSetDefaultRingtoneCalled = new HashMap<>();
+
+    public static void setRingtone(Ringtone ringtone) {
+        sRingtone = ringtone;
+    }
+
+    @Implementation
+    protected static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
+        return sRingtone;
+    }
+
+    @Implementation
+    public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
+        sSetDefaultRingtoneCalled.put(type, ringtoneUri);
+    }
+
+    @Implementation
+    public static Uri getActualDefaultRingtoneUri(Context context, int type) {
+        return sSetDefaultRingtoneCalled.getOrDefault(type, null);
+    }
+
+    @Resetter
+    public static void reset() {
+        sRingtone = null;
+        sSetDefaultRingtoneCalled.clear();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowSecureSettings.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowSecureSettings.java
new file mode 100644
index 0000000..cb2ea6c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowSecureSettings.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+@Implements(Settings.Secure.class)
+public class ShadowSecureSettings {
+
+    private static final Map<ContentResolver, Table<Integer, String, Object>> sUserDataMap =
+            new WeakHashMap<>();
+
+    @Implementation
+    protected static int getInt(ContentResolver cr, String name, int def) {
+        return getIntForUser(cr, name, def, cr.getUserId());
+    }
+
+    @Implementation
+    protected static int getIntForUser(ContentResolver resolver, String name, int def,
+            int userHandle) {
+        final Table<Integer, String, Object> userTable = getUserTable(resolver);
+        synchronized (userTable) {
+            final Object object = userTable.get(userHandle, name);
+            return object instanceof Integer ? (Integer) object : def;
+        }
+    }
+
+    @Implementation
+    protected static boolean putIntForUser(ContentResolver resolver, String name, int value,
+            int userHandle) {
+        final Table<Integer, String, Object> userTable = getUserTable(resolver);
+        synchronized (userTable) {
+            userTable.put(userHandle, name, value);
+            return true;
+        }
+    }
+
+    @Implementation
+    protected static String getString(ContentResolver resolver, String name) {
+        return getStringForUser(resolver, name, resolver.getUserId());
+    }
+
+    @Implementation
+    protected static String getStringForUser(ContentResolver resolver, String name,
+            int userHandle) {
+        final Table<Integer, String, Object> userTable = getUserTable(resolver);
+        synchronized (userTable) {
+            final Object object = userTable.get(userHandle, name);
+            return object instanceof String ? (String) object : null;
+        }
+    }
+
+    @Implementation
+    protected static boolean putString(ContentResolver resolver, String name, String value) {
+        return putStringForUser(resolver, name, value, resolver.getUserId());
+    }
+
+    @Implementation
+    protected static boolean putStringForUser(ContentResolver resolver, String name, String value,
+            int userHandle) {
+        final Table<Integer, String, Object> userTable = getUserTable(resolver);
+        synchronized (userTable) {
+            userTable.put(userHandle, name, value);
+            return true;
+        }
+    }
+
+    /**
+     * Clears the UserDataMap for Robotests.
+     */
+    @Resetter
+    public static void reset() {
+        synchronized (sUserDataMap) {
+            sUserDataMap.clear();
+        }
+    }
+
+    private static Table<Integer, String, Object> getUserTable(ContentResolver contentResolver) {
+        synchronized (sUserDataMap) {
+            Table<Integer, String, Object> table = sUserDataMap.get(contentResolver);
+            if (table == null) {
+                table = HashBasedTable.create();
+                sUserDataMap.put(contentResolver, table);
+            }
+            return table;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowSmsApplication.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowSmsApplication.java
new file mode 100644
index 0000000..4af6ff9
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowSmsApplication.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.internal.telephony.SmsApplication;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(SmsApplication.class)
+public class ShadowSmsApplication {
+
+    private static ComponentName sDefaultSmsApplication;
+
+    @Resetter
+    public static void reset() {
+        sDefaultSmsApplication = null;
+    }
+
+    @Implementation
+    protected static ComponentName getDefaultSmsApplication(
+            Context context, boolean updateIfNeeded) {
+        return sDefaultSmsApplication;
+    }
+
+    public static void setDefaultSmsApplication(ComponentName defaultSmsApplication) {
+        sDefaultSmsApplication = defaultSmsApplication;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowStorageManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowStorageManager.java
new file mode 100644
index 0000000..0fc2bb4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowStorageManager.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.ArrayMap;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.List;
+import java.util.Map;
+
+@Implements(StorageManager.class)
+public class ShadowStorageManager {
+
+    private List<VolumeInfo> mVolumes;
+    private Map<VolumeInfo, String> mBestVolumeDescriptions = new ArrayMap<>();
+
+    public void setVolumes(List<VolumeInfo> volumes) {
+        mVolumes = volumes;
+    }
+
+    @Implementation
+    protected List<VolumeInfo> getVolumes() {
+        return mVolumes;
+    }
+
+    public void setBestVolumeDescription(VolumeInfo volume, String description) {
+        mBestVolumeDescriptions.put(volume, description);
+    }
+
+    @Implementation
+    protected String getBestVolumeDescription(VolumeInfo volume) {
+        return mBestVolumeDescriptions.get(volume);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowStorageManagerVolumeProvider.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowStorageManagerVolumeProvider.java
new file mode 100644
index 0000000..5a02ae4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowStorageManagerVolumeProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.os.storage.VolumeInfo;
+
+import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(StorageManagerVolumeProvider.class)
+public class ShadowStorageManagerVolumeProvider {
+
+    private static VolumeInfo sVolumeInfo;
+
+    @Resetter
+    public static void reset() {
+        sVolumeInfo = null;
+    }
+
+    @Implementation
+    protected VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume) {
+        return sVolumeInfo;
+    }
+
+    public static void setVolumeInfo(VolumeInfo volumeInfo) {
+        sVolumeInfo = volumeInfo;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowSubscriptionManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowSubscriptionManager.java
new file mode 100644
index 0000000..98fd592
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowSubscriptionManager.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import android.annotation.NonNull;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.List;
+
+@Implements(SubscriptionManager.class)
+public class ShadowSubscriptionManager {
+
+    private static int sDefaultDataSubscriptionId = INVALID_SUBSCRIPTION_ID;
+    private static int sDefaultVoiceSubscriptionId = INVALID_SUBSCRIPTION_ID;
+    private static int sDefaultSmsSubscriptionId = INVALID_SUBSCRIPTION_ID;
+    private static int sDefaultSubscriptionId = INVALID_SUBSCRIPTION_ID;
+    private static SubscriptionInfo sDefaultDataSubscriptionInfo = null;
+
+    private List<SubscriptionInfo> mSubscriptionInfoList;
+    private List<SubscriptionPlan> mSubscriptionPlanList;
+
+    @Implementation
+    public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
+        return mSubscriptionInfoList;
+    }
+
+    public void setActiveSubscriptionInfoList(List<SubscriptionInfo> subscriptionInfoList) {
+        mSubscriptionInfoList = subscriptionInfoList;
+    }
+
+    @Implementation
+    protected @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) {
+        return mSubscriptionPlanList;
+    }
+
+    public void setSubscriptionPlans(List<SubscriptionPlan> subscriptionPlanList) {
+        mSubscriptionPlanList = subscriptionPlanList;
+    }
+
+    @Implementation
+    public static int getDefaultDataSubscriptionId() {
+        return sDefaultDataSubscriptionId;
+    }
+
+    @Implementation
+    public static void setDefaultDataSubId(int subId) {
+        sDefaultDataSubscriptionId = subId;
+    }
+
+    @Implementation
+    public static int getDefaultVoiceSubscriptionId() {
+        return sDefaultVoiceSubscriptionId;
+    }
+
+    @Implementation
+    public static void setDefaultVoiceSubId(int subId) {
+        sDefaultVoiceSubscriptionId = subId;
+    }
+
+    @Implementation
+    public static int getDefaultSmsSubscriptionId() {
+        return sDefaultSmsSubscriptionId;
+    }
+
+    @Implementation
+    public static void setDefaultSmsSubId(int subId) {
+        sDefaultSmsSubscriptionId = subId;
+    }
+
+    @Implementation
+    public static int getDefaultSubscriptionId() {
+        return sDefaultSubscriptionId;
+    }
+
+    public static void setDefaultSubscriptionId(int subId) {
+        sDefaultSubscriptionId = subId;
+    }
+
+    @Implementation
+    public SubscriptionInfo getDefaultDataSubscriptionInfo() {
+        return sDefaultDataSubscriptionInfo;
+    }
+
+    public static void setDefaultDataSubscriptionInfo(SubscriptionInfo subscriptionInfo) {
+        sDefaultDataSubscriptionInfo = subscriptionInfo;
+    }
+
+    /** Resets this shadow to its initial state for static values. */
+    public static void reset() {
+        sDefaultDataSubscriptionId = INVALID_SUBSCRIPTION_ID;
+        sDefaultVoiceSubscriptionId = INVALID_SUBSCRIPTION_ID;
+        sDefaultSmsSubscriptionId = INVALID_SUBSCRIPTION_ID;
+        sDefaultSubscriptionId = INVALID_SUBSCRIPTION_ID;
+        sDefaultDataSubscriptionInfo = null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowTelephonyManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowTelephonyManager.java
new file mode 100644
index 0000000..4c96d06
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowTelephonyManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import static android.telephony.PhoneStateListener.LISTEN_NONE;
+
+import android.telephony.PhoneStateListener;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Implements(value = TelephonyManager.class, inheritImplementationMethods = true)
+public class ShadowTelephonyManager extends org.robolectric.shadows.ShadowTelephonyManager {
+
+    public static final String SUBSCRIBER_ID = "test_id";
+    private static Map<Integer, Integer> sSubIdsWithResetCalledCount = new HashMap<>();
+    private final Map<PhoneStateListener, Integer> mPhoneStateRegistrations = new HashMap<>();
+    private boolean mIsDataEnabled = false;
+
+    public static boolean verifyFactoryResetCalled(int subId, int numTimes) {
+        if (!sSubIdsWithResetCalledCount.containsKey(subId)) return false;
+        return sSubIdsWithResetCalledCount.get(subId) == numTimes;
+    }
+
+    @Implementation
+    public void listen(PhoneStateListener listener, int flags) {
+        super.listen(listener, flags);
+
+        if (flags == LISTEN_NONE) {
+            mPhoneStateRegistrations.remove(listener);
+        } else {
+            mPhoneStateRegistrations.put(listener, flags);
+        }
+    }
+
+    public List<PhoneStateListener> getListenersForFlags(int flags) {
+        List<PhoneStateListener> listeners = new ArrayList<>();
+        for (PhoneStateListener listener : mPhoneStateRegistrations.keySet()) {
+            if ((mPhoneStateRegistrations.get(listener) & flags) != 0) {
+                listeners.add(listener);
+            }
+        }
+        return listeners;
+    }
+
+    @Implementation
+    public void setDataEnabled(boolean enable) {
+        mIsDataEnabled = enable;
+    }
+
+    @Implementation
+    public boolean isDataEnabled() {
+        return mIsDataEnabled;
+    }
+
+    @Implementation
+    protected void factoryReset(int subId) {
+        sSubIdsWithResetCalledCount.put(subId,
+                sSubIdsWithResetCalledCount.getOrDefault(subId, 0) + 1);
+    }
+
+    @Implementation
+    protected String getSubscriberId(int subId) {
+        return subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID ? null : SUBSCRIBER_ID;
+    }
+
+    @Resetter
+    public static void reset() {
+        sSubIdsWithResetCalledCount.clear();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowTextListItem.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowTextListItem.java
deleted file mode 100644
index f093f2c..0000000
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowTextListItem.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.testutils;
-
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import androidx.car.widget.TextListItem;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-/**
- * Allows access to the internals of a text list item for testing.
- */
-@Implements(TextListItem.class)
-public class ShadowTextListItem {
-    private Drawable mDrawable;
-    private int mSupplementalIconDrawableId;
-    private String mTitle;
-    private String mBody;
-    private View.OnClickListener mOnClickListener;
-
-    @Implementation
-    public void setPrimaryActionIcon(Drawable drawable, boolean useLargeIcon) {
-        mDrawable = drawable;
-    }
-
-    @Implementation
-    public void setSupplementalIcon(int iconResId, boolean showDivider) {
-        mSupplementalIconDrawableId = iconResId;
-    }
-
-    @Implementation
-    public void setTitle(String title) {
-        mTitle = title;
-    }
-
-    @Implementation
-    public void setBody(String body) {
-        mBody = body;
-    }
-
-    @Implementation
-    public void setBody(String body, boolean asPrimary) {
-        mBody = body;
-    }
-
-    @Implementation
-    public void setOnClickListener(View.OnClickListener listener) {
-        mOnClickListener = listener;
-    }
-
-    /**
-     * Returns the drawable set on this item.
-     */
-    public Drawable getDrawable() {
-        return mDrawable;
-    }
-
-    /**
-     * Returns the supplemental drawable id set on this item.
-     */
-    public int getSupplementalIconDrawableId() {
-        return mSupplementalIconDrawableId;
-    }
-
-    /**
-     * Returns the title set on this item.
-     */
-    public String getTitle() {
-        return mTitle;
-    }
-
-    /**
-     * Returns the body set on this item.
-     */
-    public String getBody() {
-        return mBody;
-    }
-
-    /**
-     * Returns the onclick listener for this item.
-     */
-    public View.OnClickListener getOnClickListener() {
-        return mOnClickListener;
-    }
-}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowTextToSpeech.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowTextToSpeech.java
new file mode 100644
index 0000000..cdc4b39
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowTextToSpeech.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.Voice;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.Locale;
+
+@Implements(TextToSpeech.class)
+public class ShadowTextToSpeech {
+
+    private static TextToSpeech sInstance;
+    private static TextToSpeech.OnInitListener sOnInitListener;
+    private static String sEngine;
+
+    public static void setInstance(TextToSpeech textToSpeech) {
+        sInstance = textToSpeech;
+    }
+
+    /**
+     * Override constructor and only store the name of the last constructed engine and init
+     * listener.
+     */
+    public void __constructor__(Context context, TextToSpeech.OnInitListener listener,
+            String engine,
+            String packageName, boolean useFallback) {
+        sOnInitListener = listener;
+        sEngine = engine;
+    }
+
+    public void __constructor__(Context context, TextToSpeech.OnInitListener listener,
+            String engine) {
+        __constructor__(context, listener, engine, null, false);
+    }
+
+    public void __constructor__(Context context, TextToSpeech.OnInitListener listener) {
+        __constructor__(context, listener, null, null, false);
+    }
+
+    @Implementation
+    protected String getCurrentEngine() {
+        return sInstance.getCurrentEngine();
+    }
+
+    @Implementation
+    protected int setLanguage(final Locale loc) {
+        return sInstance.setLanguage(loc);
+    }
+
+    @Implementation
+    protected void shutdown() {
+        sInstance.shutdown();
+    }
+
+    @Implementation
+    protected int setSpeechRate(float speechRate) {
+        return sInstance.setSpeechRate(speechRate);
+    }
+
+    @Implementation
+    protected int setPitch(float pitch) {
+        return sInstance.setPitch(pitch);
+    }
+
+    @Implementation
+    protected Voice getVoice() {
+        return sInstance.getVoice();
+    }
+
+    @Implementation
+    protected int isLanguageAvailable(final Locale loc) {
+        return sInstance.isLanguageAvailable(loc);
+    }
+
+    @Implementation
+    protected int speak(final CharSequence text,
+            final int queueMode,
+            final Bundle params,
+            final String utteranceId) {
+        return sInstance.speak(text, queueMode, params, utteranceId);
+    }
+
+    @Resetter
+    public static void reset() {
+        sInstance = null;
+        sOnInitListener = null;
+        sEngine = null;
+    }
+
+    /** Check for the last constructed engine name. */
+    public static String getLastConstructedEngine() {
+        return sEngine;
+    }
+
+    /** Trigger the initializtion callback given the input status. */
+    public static void callInitializationCallbackWithStatus(int status) {
+        sOnInitListener.onInit(status);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowTtsEngines.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowTtsEngines.java
new file mode 100644
index 0000000..df3eef8
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowTtsEngines.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.Intent;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.List;
+import java.util.Locale;
+
+@Implements(TtsEngines.class)
+public class ShadowTtsEngines {
+    private static TtsEngines sInstance;
+
+    public static void setInstance(TtsEngines ttsEngines) {
+        sInstance = ttsEngines;
+    }
+
+    @Resetter
+    public static void reset() {
+        sInstance = null;
+    }
+
+    @Implementation
+    protected List<TextToSpeech.EngineInfo> getEngines() {
+        return sInstance.getEngines();
+    }
+
+    @Implementation
+    protected TextToSpeech.EngineInfo getEngineInfo(String packageName) {
+        return sInstance.getEngineInfo(packageName);
+    }
+
+    @Implementation
+    protected String getDefaultEngine() {
+        return sInstance.getDefaultEngine();
+    }
+
+    @Implementation
+    protected Intent getSettingsIntent(String engine) {
+        return sInstance.getSettingsIntent(engine);
+    }
+
+    @Implementation
+    protected boolean isLocaleSetToDefaultForEngine(String engineName) {
+        return sInstance.isLocaleSetToDefaultForEngine(engineName);
+    }
+
+    @Implementation
+    protected Locale getLocalePrefForEngine(String engineName) {
+        return sInstance.getLocalePrefForEngine(engineName);
+    }
+
+    @Implementation
+    protected synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) {
+        sInstance.updateLocalePrefForEngine(engineName, newLocale);
+    }
+
+    @Implementation
+    protected Locale parseLocaleString(String localeString) {
+        return sInstance.parseLocaleString(localeString);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowUidDetailProvider.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowUidDetailProvider.java
new file mode 100644
index 0000000..2db468a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowUidDetailProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 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.car.settings.testutils;
+
+import com.android.settingslib.net.UidDetail;
+import com.android.settingslib.net.UidDetailProvider;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(UidDetailProvider.class)
+public class ShadowUidDetailProvider {
+
+    private static UidDetail sUidDetail;
+
+    @Resetter
+    public static void reset() {
+        sUidDetail = null;
+    }
+
+    @Implementation
+    public UidDetail getUidDetail(int uid, boolean blocking) {
+        return sUidDetail;
+    }
+
+    public static void setUidDetail(UidDetail uidDetail) {
+        sUidDetail = uidDetail;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowUserIconProvider.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowUserIconProvider.java
index c4bcf1d..acddacc 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowUserIconProvider.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowUserIconProvider.java
@@ -27,7 +27,12 @@
 @Implements(UserIconProvider.class)
 public class ShadowUserIconProvider {
     @Implementation
-    public Drawable getUserIcon(UserInfo userInfo, Context context) {
+    protected Drawable getUserIcon(UserInfo userInfo, Context context) {
+        return null;
+    }
+
+    @Implementation
+    protected Drawable getDefaultGuestIcon(Context context) {
         return null;
     }
 }
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowUserManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowUserManager.java
new file mode 100644
index 0000000..eef412d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowUserManager.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.util.ArrayMap;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+@Implements(UserManager.class)
+public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
+    private static UserManager sInstance;
+
+    private Map<Integer, List<UserInfo>> mProfiles = new ArrayMap<>();
+
+    public static void setInstance(UserManager manager) {
+        sInstance = manager;
+    }
+
+    @Implementation
+    protected UserInfo getUserInfo(@UserIdInt int userHandle) {
+        return sInstance.getUserInfo(userHandle);
+    }
+
+    @Implementation
+    protected int[] getProfileIdsWithDisabled(int userId) {
+        if (mProfiles.containsKey(userId)) {
+            return mProfiles.get(userId).stream().mapToInt(userInfo -> userInfo.id).toArray();
+        }
+        return new int[]{};
+    }
+
+    @Implementation
+    @Override
+    protected List<UserInfo> getProfiles(int userHandle) {
+        if (mProfiles.containsKey(userHandle)) {
+            return new ArrayList<>(mProfiles.get(userHandle));
+        }
+        return Collections.emptyList();
+    }
+
+    /** Adds a profile to be returned by {@link #getProfiles(int)}. **/
+    public void addProfile(
+            int userHandle, int profileUserHandle, String profileName, int profileFlags) {
+        mProfiles.putIfAbsent(userHandle, new ArrayList<>());
+        mProfiles.get(userHandle).add(new UserInfo(profileUserHandle, profileName, profileFlags));
+    }
+
+    @Resetter
+    public static void reset() {
+        sInstance = null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowUtils.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowUtils.java
new file mode 100644
index 0000000..0bacf94
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.res.Resources;
+
+import com.android.settingslib.Utils;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(value = Utils.class)
+public class ShadowUtils {
+    private static String sDeviceProvisioningPackage;
+
+    @Resetter
+    public static void reset() {
+        sDeviceProvisioningPackage = null;
+    }
+
+    @Implementation
+    protected static boolean isDeviceProvisioningPackage(Resources resources,
+            String packageName) {
+        return sDeviceProvisioningPackage != null && sDeviceProvisioningPackage.equals(
+                packageName);
+    }
+
+    public static void setDeviceProvisioningPackage(String deviceProvisioningPackage) {
+        sDeviceProvisioningPackage = deviceProvisioningPackage;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowVoiceInteractionServiceInfo.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowVoiceInteractionServiceInfo.java
new file mode 100644
index 0000000..936d0b4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowVoiceInteractionServiceInfo.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.service.voice.VoiceInteractionServiceInfo;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Implements(VoiceInteractionServiceInfo.class)
+public class ShadowVoiceInteractionServiceInfo {
+    private static Map<ServiceInfo, Boolean> sSupportsAssistMap = new HashMap<>();
+    private static Map<ServiceInfo, String> sRecognitionServiceMap = new HashMap<>();
+    private static Map<ServiceInfo, String> sSettingsActivityMap = new HashMap<>();
+
+    private ServiceInfo mServiceInfo;
+
+    public void __constructor__(PackageManager pm, ServiceInfo si) {
+        mServiceInfo = si;
+    }
+
+    public static void setSupportsAssist(ServiceInfo si, boolean supports) {
+        sSupportsAssistMap.put(si, supports);
+    }
+
+    public static void setRecognitionService(ServiceInfo si, String recognitionService) {
+        sRecognitionServiceMap.put(si, recognitionService);
+    }
+
+    public static void setSettingsActivity(ServiceInfo si, String settingsActivity) {
+        sSettingsActivityMap.put(si, settingsActivity);
+    }
+
+    @Implementation
+    protected boolean getSupportsAssist() {
+        return sSupportsAssistMap.get(mServiceInfo);
+    }
+
+    @Implementation
+    protected String getRecognitionService() {
+        return sRecognitionServiceMap.get(mServiceInfo);
+    }
+
+    @Implementation
+    protected String getSettingsActivity() {
+        return sSettingsActivityMap.get(mServiceInfo);
+    }
+
+    @Implementation
+    protected ServiceInfo getServiceInfo() {
+        return mServiceInfo;
+    }
+
+    @Resetter
+    public static void reset() {
+        sSupportsAssistMap.clear();
+        sRecognitionServiceMap.clear();
+        sSettingsActivityMap.clear();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowWifiManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowWifiManager.java
new file mode 100644
index 0000000..43c0710
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowWifiManager.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.car.settings.testutils;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Implements(value = WifiManager.class, inheritImplementationMethods = true)
+public class ShadowWifiManager extends org.robolectric.shadows.ShadowWifiManager {
+
+    private static int sResetCalledCount = 0;
+
+    private final Map<Integer, WifiConfiguration> mNetworkIdToConfiguredNetworks =
+            new LinkedHashMap<>();
+
+    @Implementation
+    @Override
+    public int addNetwork(WifiConfiguration config) {
+        int networkId = mNetworkIdToConfiguredNetworks.size();
+        config.networkId = -1;
+        mNetworkIdToConfiguredNetworks.put(networkId, makeCopy(config, networkId));
+        return networkId;
+    }
+
+    public WifiConfiguration getLastAddedNetworkConfiguration() {
+        return mNetworkIdToConfiguredNetworks.get(getLastAddedNetworkId());
+    }
+
+    public int getLastAddedNetworkId() {
+        return mNetworkIdToConfiguredNetworks.size() - 1;
+    }
+
+    public static boolean verifyFactoryResetCalled(int numTimes) {
+        return sResetCalledCount == numTimes;
+    }
+
+    @Implementation
+    protected void factoryReset() {
+        sResetCalledCount++;
+    }
+
+    @Resetter
+    public static void reset() {
+        sResetCalledCount = 0;
+    }
+
+    private WifiConfiguration makeCopy(WifiConfiguration config, int networkId) {
+        WifiConfiguration copy = Shadows.shadowOf(config).copy();
+        copy.networkId = networkId;
+        return copy;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/tts/PreferredEngineEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/tts/PreferredEngineEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..c945d60
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/tts/PreferredEngineEntryPreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowTtsEngines;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowTtsEngines.class})
+public class PreferredEngineEntryPreferenceControllerTest {
+
+    private static final TextToSpeech.EngineInfo ENGINE_INFO = new TextToSpeech.EngineInfo();
+    private static final String INTENT_ACTION = "test_action";
+
+    static {
+        ENGINE_INFO.label = "Test Engine";
+        ENGINE_INFO.name = "com.android.car.settings.tts.test.Engine";
+    }
+
+    private ButtonPreference mPreference;
+    @Mock
+    private TtsEngines mEnginesHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowTtsEngines.setInstance(mEnginesHelper);
+        Context context = RuntimeEnvironment.application;
+
+        mPreference = new ButtonPreference(context);
+        PreferenceControllerTestHelper<PreferredEngineEntryPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(context,
+                        PreferredEngineEntryPreferenceController.class, mPreference);
+
+        Intent intent = new Intent(INTENT_ACTION);
+        when(mEnginesHelper.getSettingsIntent(ENGINE_INFO.name)).thenReturn(intent);
+        when(mEnginesHelper.getEngineInfo(ENGINE_INFO.name)).thenReturn(
+                ENGINE_INFO);
+        when(mEnginesHelper.getDefaultEngine()).thenReturn(ENGINE_INFO.name);
+
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowTtsEngines.reset();
+    }
+
+    @Test
+    public void performButtonClick_navigateToNextActivity() {
+        mPreference.performButtonClick();
+
+        Intent actual = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(actual.getAction()).isEqualTo(INTENT_ACTION);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/tts/PreferredEngineOptionsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/tts/PreferredEngineOptionsPreferenceControllerTest.java
new file mode 100644
index 0000000..9134b47
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/tts/PreferredEngineOptionsPreferenceControllerTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowTextToSpeech;
+import com.android.car.settings.testutils.ShadowTtsEngines;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowTtsEngines.class, ShadowTextToSpeech.class})
+public class PreferredEngineOptionsPreferenceControllerTest {
+    private static final TextToSpeech.EngineInfo OTHER_ENGINE_INFO = new TextToSpeech.EngineInfo();
+    private static final TextToSpeech.EngineInfo CURRENT_ENGINE_INFO =
+            new TextToSpeech.EngineInfo();
+
+    static {
+        OTHER_ENGINE_INFO.label = "Test Engine 1";
+        OTHER_ENGINE_INFO.name = "com.android.car.settings.tts.test.Engine1";
+        CURRENT_ENGINE_INFO.label = "Test Engine 2";
+        CURRENT_ENGINE_INFO.name = "com.android.car.settings.tts.test.Engine2";
+    }
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<PreferredEngineOptionsPreferenceController>
+            mControllerHelper;
+    private PreferredEngineOptionsPreferenceController mController;
+    private PreferenceGroup mPreferenceGroup;
+    @Mock
+    private TtsEngines mEnginesHelper;
+    @Mock
+    private TextToSpeech mTextToSpeech;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowTtsEngines.setInstance(mEnginesHelper);
+        ShadowTextToSpeech.setInstance(mTextToSpeech);
+
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                PreferredEngineOptionsPreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+
+        when(mEnginesHelper.getEngines()).thenReturn(
+                Arrays.asList(OTHER_ENGINE_INFO, CURRENT_ENGINE_INFO));
+        when(mEnginesHelper.getEngineInfo(OTHER_ENGINE_INFO.name)).thenReturn(OTHER_ENGINE_INFO);
+        when(mEnginesHelper.getEngineInfo(CURRENT_ENGINE_INFO.name)).thenReturn(
+                CURRENT_ENGINE_INFO);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowTtsEngines.reset();
+        ShadowTextToSpeech.reset();
+    }
+
+    @Test
+    public void onCreate_populatesGroup() {
+        when(mTextToSpeech.getCurrentEngine()).thenReturn(CURRENT_ENGINE_INFO.name);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void refreshUi_currentEngineInfoSummarySet() {
+        when(mTextToSpeech.getCurrentEngine()).thenReturn(CURRENT_ENGINE_INFO.name);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(
+                mPreferenceGroup.findPreference(CURRENT_ENGINE_INFO.name).getSummary()).isEqualTo(
+                mContext.getString(R.string.text_to_speech_current_engine));
+    }
+
+    @Test
+    public void refreshUi_otherEngineInfoSummaryEmpty() {
+        when(mTextToSpeech.getCurrentEngine()).thenReturn(CURRENT_ENGINE_INFO.name);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.findPreference(OTHER_ENGINE_INFO.name).getSummary()).isEqualTo(
+                "");
+    }
+
+    @Test
+    public void performClick_currentEngine_returnFalse() {
+        when(mTextToSpeech.getCurrentEngine()).thenReturn(CURRENT_ENGINE_INFO.name);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        Preference currentEngine = mPreferenceGroup.findPreference(CURRENT_ENGINE_INFO.name);
+        assertThat(currentEngine.getOnPreferenceClickListener().onPreferenceClick(
+                currentEngine)).isFalse();
+    }
+
+    @Test
+    public void performClick_otherEngine_returnTrue() {
+        when(mTextToSpeech.getCurrentEngine()).thenReturn(CURRENT_ENGINE_INFO.name);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        Preference otherEngine = mPreferenceGroup.findPreference(OTHER_ENGINE_INFO.name);
+        assertThat(otherEngine.getOnPreferenceClickListener().onPreferenceClick(
+                otherEngine)).isTrue();
+    }
+
+    @Test
+    public void performClick_otherEngine_initSuccess_changeCurrentEngine() {
+        when(mTextToSpeech.getCurrentEngine()).thenReturn(CURRENT_ENGINE_INFO.name);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        Preference otherEngine = mPreferenceGroup.findPreference(OTHER_ENGINE_INFO.name);
+        otherEngine.performClick();
+
+        ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
+        assertThat(ShadowTextToSpeech.getLastConstructedEngine()).isEqualTo(OTHER_ENGINE_INFO.name);
+    }
+
+    @Test
+    public void performClick_otherEngine_initFail_keepCurrentEngine() {
+        when(mTextToSpeech.getCurrentEngine()).thenReturn(CURRENT_ENGINE_INFO.name);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        Preference otherEngine = mPreferenceGroup.findPreference(OTHER_ENGINE_INFO.name);
+        otherEngine.performClick();
+
+        ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.ERROR);
+        assertThat(ShadowTextToSpeech.getLastConstructedEngine()).isEqualTo(
+                CURRENT_ENGINE_INFO.name);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/tts/TtsPlaybackPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/tts/TtsPlaybackPreferenceControllerTest.java
new file mode 100644
index 0000000..f5f1df8
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/tts/TtsPlaybackPreferenceControllerTest.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+import android.speech.tts.Voice;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ActivityResultCallback;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.SeekBarPreference;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+import com.android.car.settings.testutils.ShadowTextToSpeech;
+import com.android.car.settings.testutils.ShadowTtsEngines;
+
+import com.google.android.collect.Lists;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Locale;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowTextToSpeech.class, ShadowTtsEngines.class,
+        ShadowSecureSettings.class})
+public class TtsPlaybackPreferenceControllerTest {
+
+    private static final String DEFAULT_ENGINE_NAME = "com.android.car.settings.tts.test.default";
+    private static final TextToSpeech.EngineInfo ENGINE_INFO = new TextToSpeech.EngineInfo();
+    private static final Voice VOICE = new Voice("Test Name", Locale.ENGLISH, /* quality= */0,
+            /* latency= */ 0, /* requiresNetworkConnection= */ true, /* features= */ null);
+
+    static {
+        ENGINE_INFO.label = "Test Engine";
+        ENGINE_INFO.name = "com.android.car.settings.tts.test.other";
+    }
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<TtsPlaybackPreferenceController>
+            mControllerHelper;
+    private TtsPlaybackPreferenceController mController;
+    private PreferenceGroup mPreferenceGroup;
+    private ListPreference mDefaultLanguagePreference;
+    private SeekBarPreference mSpeechRatePreference;
+    private SeekBarPreference mVoicePitchPreference;
+    private Preference mResetPreference;
+    @Mock
+    private TextToSpeech mTextToSpeech;
+    @Mock
+    private TtsEngines mEnginesHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowTextToSpeech.setInstance(mTextToSpeech);
+        ShadowTtsEngines.setInstance(mEnginesHelper);
+
+        mContext = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                TtsPlaybackPreferenceController.class, mPreferenceGroup);
+
+        mDefaultLanguagePreference = new ListPreference(mContext);
+        mDefaultLanguagePreference.setKey(mContext.getString(R.string.pk_tts_default_language));
+        mPreferenceGroup.addPreference(mDefaultLanguagePreference);
+
+        mSpeechRatePreference = new SeekBarPreference(mContext);
+        mSpeechRatePreference.setKey(mContext.getString(R.string.pk_tts_speech_rate));
+        mPreferenceGroup.addPreference(mSpeechRatePreference);
+
+        mVoicePitchPreference = new SeekBarPreference(mContext);
+        mVoicePitchPreference.setKey(mContext.getString(R.string.pk_tts_pitch));
+        mPreferenceGroup.addPreference(mVoicePitchPreference);
+
+        mResetPreference = new Preference(mContext);
+        mResetPreference.setKey(mContext.getString(R.string.pk_tts_reset));
+        mPreferenceGroup.addPreference(mResetPreference);
+
+        mController = mControllerHelper.getController();
+
+        when(mTextToSpeech.getCurrentEngine()).thenReturn(ENGINE_INFO.name);
+        when(mTextToSpeech.getVoice()).thenReturn(VOICE);
+        when(mEnginesHelper.parseLocaleString(VOICE.getLocale().toString())).thenReturn(
+                VOICE.getLocale());
+        when(mEnginesHelper.parseLocaleString(Locale.CANADA.toString())).thenReturn(Locale.CANADA);
+        when(mEnginesHelper.parseLocaleString(Locale.KOREA.toString())).thenReturn(Locale.KOREA);
+
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_RATE,
+                TextToSpeech.Engine.DEFAULT_RATE);
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_PITCH,
+                TextToSpeech.Engine.DEFAULT_PITCH);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowTtsEngines.reset();
+        ShadowTextToSpeech.reset();
+        ShadowSecureSettings.reset();
+    }
+
+    @Test
+    public void onCreate_startsCheckVoiceData() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mControllerHelper.getMockFragmentController()).startActivityForResult(
+                intent.capture(), eq(TtsPlaybackPreferenceController.VOICE_DATA_CHECK),
+                any(ActivityResultCallback.class));
+
+        assertThat(intent.getValue().getAction()).isEqualTo(
+                TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
+        assertThat(intent.getValue().getPackage()).isEqualTo(ENGINE_INFO.name);
+    }
+
+    @Test
+    public void voiceDataCheck_processActivityResult_dataIsNull_defaultSynthRemainsUnchanged() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_SYNTH,
+                DEFAULT_ENGINE_NAME);
+
+        mController.processActivityResult(
+                TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
+                TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, /* data= */ null);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_SYNTH)).isEqualTo(DEFAULT_ENGINE_NAME);
+    }
+
+    @Test
+    public void voiceDataCheck_processActivityResult_dataIsNotNull_updatesDefaultSynth() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_SYNTH,
+                DEFAULT_ENGINE_NAME);
+
+        Intent data = new Intent();
+        mController.processActivityResult(
+                TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
+                TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
+
+        assertThat(Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_SYNTH)).isEqualTo(ENGINE_INFO.name);
+    }
+
+    @Test
+    public void voiceDataCheck_processActivityResult_checkSuccess_hasVoices_populatesPreference() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        // Check that the length is 0 initially.
+        assertThat(mDefaultLanguagePreference.getEntries()).isNull();
+
+        Intent data = new Intent();
+        data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
+                Lists.newArrayList(
+                        Locale.ENGLISH.toString(),
+                        Locale.CANADA.toString(),
+                        Locale.KOREA.toString()
+                ));
+
+        ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
+        mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
+                TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
+
+        // Length is 3 languages + default language.
+        assertThat(mDefaultLanguagePreference.getEntries().length).isEqualTo(4);
+    }
+
+    @Test
+    public void getSampleText_processActivityResult_dataIsNull_setsDefaultText() {
+        mController.processActivityResult(TtsPlaybackPreferenceController.GET_SAMPLE_TEXT,
+                TextToSpeech.LANG_AVAILABLE, /* data= */ null);
+
+        assertThat(mController.getSampleText()).isEqualTo(
+                mContext.getString(R.string.tts_default_sample_string));
+    }
+
+    @Test
+    public void getSampleText_processActivityResult_emptyText_setsDefaultText() {
+        String testData = "";
+
+        Intent data = new Intent();
+        data.putExtra(TextToSpeech.Engine.EXTRA_SAMPLE_TEXT, testData);
+
+        mController.processActivityResult(TtsPlaybackPreferenceController.GET_SAMPLE_TEXT,
+                TextToSpeech.LANG_AVAILABLE, data);
+
+        assertThat(mController.getSampleText()).isEqualTo(
+                mContext.getString(R.string.tts_default_sample_string));
+    }
+
+    @Test
+    public void getSampleText_processActivityResult_dataIsNotNull_setsCorrectText() {
+        String testData = "Test sample text";
+
+        Intent data = new Intent();
+        data.putExtra(TextToSpeech.Engine.EXTRA_SAMPLE_TEXT, testData);
+
+        mController.processActivityResult(TtsPlaybackPreferenceController.GET_SAMPLE_TEXT,
+                TextToSpeech.LANG_AVAILABLE, data);
+
+        assertThat(mController.getSampleText()).isEqualTo(testData);
+    }
+
+    @Test
+    public void defaultLanguage_handlePreferenceChanged_passEmptyValue_setsDefault() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        Intent data = new Intent();
+        data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
+                Lists.newArrayList(
+                        Locale.ENGLISH.toString(),
+                        Locale.CANADA.toString(),
+                        Locale.KOREA.toString()
+                ));
+        mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
+                TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
+
+        // Test change listener.
+        mDefaultLanguagePreference.callChangeListener("");
+
+        verify(mTextToSpeech).setLanguage(Locale.getDefault());
+    }
+
+    @Test
+    public void defaultLanguage_handlePreferenceChanged_passLocale_setsLocale() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        Intent data = new Intent();
+        data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
+                Lists.newArrayList(
+                        Locale.ENGLISH.toString(),
+                        Locale.CANADA.toString(),
+                        Locale.KOREA.toString()
+                ));
+        mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
+                TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
+
+        // Test change listener.
+        mDefaultLanguagePreference.callChangeListener(Locale.ENGLISH.toString());
+
+        verify(mTextToSpeech).setLanguage(Locale.ENGLISH);
+    }
+
+    @Test
+    public void defaultLanguage_handlePreferenceChanged_passLocale_setsSummary() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        Intent data = new Intent();
+        data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
+                Lists.newArrayList(
+                        Locale.ENGLISH.toString(),
+                        Locale.CANADA.toString(),
+                        Locale.KOREA.toString()
+                ));
+        mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
+                TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
+        mDefaultLanguagePreference.callChangeListener(Locale.ENGLISH.toString());
+
+        assertThat(mDefaultLanguagePreference.getSummary()).isEqualTo(
+                Locale.ENGLISH.getDisplayName());
+    }
+
+    @Test
+    public void speechRate_handlePreferenceChanged_updatesSecureSettings() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
+        mSpeechRatePreference.callChangeListener(newSpeechRate);
+
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE)).isEqualTo(
+                newSpeechRate);
+    }
+
+    @Test
+    public void speechRate_handlePreferenceChanged_updatesTts() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
+        mSpeechRatePreference.callChangeListener(newSpeechRate);
+
+        verify(mTextToSpeech).setSpeechRate(
+                newSpeechRate / TtsPlaybackSettingsManager.SCALING_FACTOR);
+    }
+
+    @Test
+    public void speechRate_handlePreferenceChanged_speaksSampleText() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
+        mSpeechRatePreference.callChangeListener(newSpeechRate);
+
+        verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), isNull(), eq("Sample"));
+    }
+
+    @Test
+    public void voicePitch_handlePreferenceChanged_updatesSecureSettings() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
+        mVoicePitchPreference.callChangeListener(newVoicePitch);
+
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH)).isEqualTo(
+                newVoicePitch);
+    }
+
+    @Test
+    public void voicePitch_handlePreferenceChanged_updatesTts() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
+        mVoicePitchPreference.callChangeListener(newVoicePitch);
+
+        verify(mTextToSpeech).setPitch(
+                newVoicePitch / TtsPlaybackSettingsManager.SCALING_FACTOR);
+    }
+
+    @Test
+    public void voicePitch_handlePreferenceChanged_speaksSampleText() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
+        mVoicePitchPreference.callChangeListener(newVoicePitch);
+
+        verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), isNull(), eq("Sample"));
+    }
+
+    @Test
+    public void refreshUi_notInitialized_disablesPreference() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+
+        assertThat(mDefaultLanguagePreference.isEnabled()).isFalse();
+        assertThat(mSpeechRatePreference.isEnabled()).isFalse();
+        assertThat(mVoicePitchPreference.isEnabled()).isFalse();
+        assertThat(mResetPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_initialized_defaultLocaleIsNull_disablesPreference() {
+        when(mEnginesHelper.parseLocaleString(any())).thenReturn(null);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
+
+        mController.refreshUi();
+
+        assertThat(mDefaultLanguagePreference.isEnabled()).isFalse();
+        assertThat(mSpeechRatePreference.isEnabled()).isFalse();
+        assertThat(mVoicePitchPreference.isEnabled()).isFalse();
+        assertThat(mResetPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_defaultLocaleNotSupported_disablesPreferencesExceptLanguage() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
+
+        Intent data = new Intent();
+        data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
+                Lists.newArrayList(
+                        // English is default locale, but isn't available.
+                        Locale.CANADA.toString(),
+                        Locale.KOREA.toString()
+                ));
+        mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
+                TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
+
+        mController.refreshUi();
+
+        assertThat(mDefaultLanguagePreference.isEnabled()).isTrue();
+        assertThat(mSpeechRatePreference.isEnabled()).isFalse();
+        assertThat(mVoicePitchPreference.isEnabled()).isFalse();
+        assertThat(mResetPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_initialized_defaultLocaleSupported_enablesPreference() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        ShadowTextToSpeech.callInitializationCallbackWithStatus(TextToSpeech.SUCCESS);
+
+        Intent data = new Intent();
+        data.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
+                Lists.newArrayList(
+                        Locale.ENGLISH.toString(),
+                        Locale.CANADA.toString(),
+                        Locale.KOREA.toString()
+                ));
+        mController.processActivityResult(TtsPlaybackPreferenceController.VOICE_DATA_CHECK,
+                TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, data);
+
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isEnabled()).isTrue();
+        assertThat(mDefaultLanguagePreference.isEnabled()).isTrue();
+        assertThat(mSpeechRatePreference.isEnabled()).isTrue();
+        assertThat(mVoicePitchPreference.isEnabled()).isTrue();
+        assertThat(mResetPreference.isEnabled()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/tts/TtsPlaybackSettingsManagerTest.java b/tests/robotests/src/com/android/car/settings/tts/TtsPlaybackSettingsManagerTest.java
new file mode 100644
index 0000000..4f22600
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/tts/TtsPlaybackSettingsManagerTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.tts;
+
+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.provider.Settings;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+import android.speech.tts.Voice;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+import com.android.car.settings.testutils.ShadowTextToSpeech;
+import com.android.car.settings.testutils.ShadowTtsEngines;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlertDialog;
+
+import java.util.Locale;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowTtsEngines.class, ShadowTextToSpeech.class, ShadowSecureSettings.class})
+public class TtsPlaybackSettingsManagerTest {
+
+    private static final String ENGINE_NAME = "com.android.car.settings.tts.test";
+    private static final String SAMPLE_TEXT = "Sample text";
+
+    private Context mContext;
+    private TtsPlaybackSettingsManager mPlaybackSettingsManager;
+    @Mock
+    private TextToSpeech mTts;
+    @Mock
+    private TtsEngines mEnginesHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowTextToSpeech.setInstance(mTts);
+        ShadowTtsEngines.setInstance(mEnginesHelper);
+
+        mContext = RuntimeEnvironment.application;
+        mPlaybackSettingsManager = new TtsPlaybackSettingsManager(mContext, mTts, mEnginesHelper);
+
+        when(mTts.getCurrentEngine()).thenReturn(ENGINE_NAME);
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_RATE,
+                TextToSpeech.Engine.DEFAULT_RATE);
+        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_PITCH,
+                TextToSpeech.Engine.DEFAULT_PITCH);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowTextToSpeech.reset();
+        ShadowTtsEngines.reset();
+        ShadowSecureSettings.reset();
+        ShadowAlertDialog.reset();
+    }
+
+    @Test
+    public void updateSpeechRate_updatesSetting() {
+        int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
+        mPlaybackSettingsManager.updateSpeechRate(newSpeechRate);
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE)).isEqualTo(
+                newSpeechRate);
+    }
+
+    @Test
+    public void updateSpeechRate_updatesTts() {
+        int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
+        mPlaybackSettingsManager.updateSpeechRate(newSpeechRate);
+        verify(mTts).setSpeechRate(newSpeechRate / TtsPlaybackSettingsManager.SCALING_FACTOR);
+    }
+
+    @Test
+    public void getCurrentSpeechRate_returnsCorrectRate() {
+        int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
+        mPlaybackSettingsManager.updateSpeechRate(newSpeechRate);
+
+        assertThat(mPlaybackSettingsManager.getCurrentSpeechRate()).isEqualTo(newSpeechRate);
+    }
+
+    @Test
+    public void resetSpeechRate_setsToDefault() {
+        int newSpeechRate = TextToSpeech.Engine.DEFAULT_RATE + 40;
+        mPlaybackSettingsManager.updateSpeechRate(newSpeechRate);
+
+        mPlaybackSettingsManager.resetSpeechRate();
+
+        assertThat(mPlaybackSettingsManager.getCurrentSpeechRate()).isEqualTo(
+                TextToSpeech.Engine.DEFAULT_RATE);
+    }
+
+    @Test
+    public void updateVoicePitch_updatesSetting() {
+        int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
+        mPlaybackSettingsManager.updateVoicePitch(newVoicePitch);
+        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH)).isEqualTo(
+                newVoicePitch);
+    }
+
+    @Test
+    public void updateVoicePitch_updatesTts() {
+        int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
+        mPlaybackSettingsManager.updateVoicePitch(newVoicePitch);
+        verify(mTts).setPitch(newVoicePitch / TtsPlaybackSettingsManager.SCALING_FACTOR);
+    }
+
+    @Test
+    public void getCurrentVoicePitch_returnsCorrectRate() {
+        int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
+        mPlaybackSettingsManager.updateVoicePitch(newVoicePitch);
+
+        assertThat(mPlaybackSettingsManager.getCurrentVoicePitch()).isEqualTo(newVoicePitch);
+    }
+
+    @Test
+    public void resetVoicePitch_setsToDefault() {
+        int newVoicePitch = TextToSpeech.Engine.DEFAULT_PITCH + 40;
+        mPlaybackSettingsManager.updateVoicePitch(newVoicePitch);
+
+        mPlaybackSettingsManager.resetVoicePitch();
+
+        assertThat(mPlaybackSettingsManager.getCurrentVoicePitch()).isEqualTo(
+                TextToSpeech.Engine.DEFAULT_PITCH);
+    }
+
+    @Test
+    public void getStoredTtsLocale_localeIsNotDefaultForEngine_returnsLocale() {
+        when(mEnginesHelper.isLocaleSetToDefaultForEngine(ENGINE_NAME)).thenReturn(false);
+        when(mEnginesHelper.getLocalePrefForEngine(ENGINE_NAME)).thenReturn(Locale.CHINESE);
+
+        assertThat(mPlaybackSettingsManager.getStoredTtsLocale()).isEqualTo(Locale.CHINESE);
+    }
+
+    @Test
+    public void getStoredTtsLocale_localeIsDefaultForEngine_returnsNull() {
+        when(mEnginesHelper.isLocaleSetToDefaultForEngine(ENGINE_NAME)).thenReturn(true);
+
+        assertThat(mPlaybackSettingsManager.getStoredTtsLocale()).isNull();
+    }
+
+    @Test
+    public void updateTtsLocale_nonNullLocale_setsLanguage() {
+        mPlaybackSettingsManager.updateTtsLocale(Locale.KOREAN);
+
+        verify(mTts).setLanguage(Locale.KOREAN);
+    }
+
+    @Test
+    public void updateTtsLocale_nullLocale_setsDefaultLanguage() {
+        mPlaybackSettingsManager.updateTtsLocale(null);
+
+        verify(mTts).setLanguage(Locale.getDefault());
+    }
+
+    @Test
+    public void updateTtsLocale_nonNullLocale_correctResultCode_updatesLocalePref() {
+        when(mTts.setLanguage(Locale.KOREAN)).thenReturn(TextToSpeech.LANG_AVAILABLE);
+        mPlaybackSettingsManager.updateTtsLocale(Locale.KOREAN);
+
+        verify(mEnginesHelper).updateLocalePrefForEngine(ENGINE_NAME, Locale.KOREAN);
+    }
+
+    @Test
+    public void updateTtsLocale_nonNullLocale_wrongResultCode_doesNotUpdateLocalePref() {
+        when(mTts.setLanguage(Locale.KOREAN)).thenReturn(TextToSpeech.LANG_MISSING_DATA);
+        mPlaybackSettingsManager.updateTtsLocale(Locale.KOREAN);
+
+        verify(mEnginesHelper, never()).updateLocalePrefForEngine(any(), any());
+    }
+
+    @Test
+    public void speakSampleText_requiresNetworkConnection_languageNotAvailable_showsAlert() {
+        Voice voice = new Voice("Test Name", Locale.FRANCE, /* quality= */0,
+                /* latency= */ 0, /* requiresNetworkConnection= */ true, /* features= */ null);
+        when(mTts.getVoice()).thenReturn(voice);
+        when(mEnginesHelper.parseLocaleString(Locale.FRANCE.toString())).thenReturn(Locale.FRANCE);
+        when(mTts.isLanguageAvailable(any(Locale.class))).thenReturn(
+                TextToSpeech.LANG_MISSING_DATA);
+
+        mPlaybackSettingsManager.speakSampleText(SAMPLE_TEXT);
+
+        assertThat(ShadowAlertDialog.getLatestAlertDialog()).isNotNull();
+    }
+
+    @Test
+    public void speakSampleText_requiresNetworkConnection_languageAvailable_speaksText() {
+        Voice voice = new Voice("Test Name", Locale.FRENCH, /* quality= */0,
+                /* latency= */ 0, /* requiresNetworkConnection= */ true, /* features= */ null);
+        when(mTts.getVoice()).thenReturn(voice);
+        when(mTts.isLanguageAvailable(Locale.FRENCH)).thenReturn(TextToSpeech.LANG_AVAILABLE);
+
+        mPlaybackSettingsManager.speakSampleText(SAMPLE_TEXT);
+
+        verify(mTts).speak(SAMPLE_TEXT, TextToSpeech.QUEUE_FLUSH, null, "Sample");
+    }
+
+    @Test
+    public void speakSampleText_doesNotRequireNetworkConnection_speaksText() {
+        Voice voice = new Voice("Test Name", Locale.FRENCH, /* quality= */0,
+                /* latency= */ 0, /* requiresNetworkConnection= */ false, /* features= */ null);
+        when(mTts.getVoice()).thenReturn(voice);
+
+        mPlaybackSettingsManager.speakSampleText(SAMPLE_TEXT);
+
+        verify(mTts).speak(SAMPLE_TEXT, TextToSpeech.QUEUE_FLUSH, null, "Sample");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/AddNewUserTaskTest.java b/tests/robotests/src/com/android/car/settings/users/AddNewUserTaskTest.java
index b9294f9..832dd70 100644
--- a/tests/robotests/src/com/android/car/settings/users/AddNewUserTaskTest.java
+++ b/tests/robotests/src/com/android/car/settings/users/AddNewUserTaskTest.java
@@ -19,7 +19,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
 import android.content.pm.UserInfo;
 
 import com.android.car.settings.CarSettingsRobolectricTestRunner;
@@ -48,28 +48,45 @@
 
     @Test
     public void testTaskCallsCreateNewNonAdminUser() {
-        mTask.execute("Test name");
+        String newUserName = "Test name";
+        mTask.execute(newUserName);
         Robolectric.flushBackgroundThreadScheduler();
 
-        verify(mCarUserManagerHelper).createNewNonAdminUser("Test name");
+        verify(mCarUserManagerHelper).createNewNonAdminUser(newUserName);
     }
 
     @Test
     public void testSwitchToNewUserIfUserCreated() {
-        UserInfo newUser = new UserInfo(10, "Test name", /* flags= */ 0);
-        when(mCarUserManagerHelper.createNewNonAdminUser("Test name")).thenReturn(newUser);
+        String newUserName = "Test name";
+        UserInfo newUser = new UserInfo(10, newUserName, /* flags= */ 0);
+        when(mCarUserManagerHelper.createNewNonAdminUser(newUserName)).thenReturn(newUser);
 
-        mTask.execute("Test name");
+        mTask.execute(newUserName);
         Robolectric.flushBackgroundThreadScheduler();
 
         verify(mCarUserManagerHelper).switchToUser(newUser);
     }
 
     @Test
-    public void testAddNewUserListenerCalled() {
-        mTask.execute("Test name");
+    public void testOnUserAddedSuccessCalledIfUserCreated() {
+        String newUserName = "Test name";
+        UserInfo newUser = new UserInfo(10, newUserName, /* flags= */ 0);
+        when(mCarUserManagerHelper.createNewNonAdminUser(newUserName)).thenReturn(newUser);
+
+        mTask.execute(newUserName);
         Robolectric.flushBackgroundThreadScheduler();
 
-        verify(mAddNewUserListener).onUserAdded();
+        verify(mAddNewUserListener).onUserAddedSuccess();
+    }
+
+    @Test
+    public void testOnUserAddedFailureCalledIfNullReturned() {
+        String newUserName = "Test name";
+        when(mCarUserManagerHelper.createNewNonAdminUser(newUserName)).thenReturn(null);
+
+        mTask.execute(newUserName);
+        Robolectric.flushBackgroundThreadScheduler();
+
+        verify(mAddNewUserListener).onUserAddedFailure();
     }
 }
diff --git a/tests/robotests/src/com/android/car/settings/users/ChooseNewAdminFragmentTest.java b/tests/robotests/src/com/android/car/settings/users/ChooseNewAdminFragmentTest.java
new file mode 100644
index 0000000..b6f41b5
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/ChooseNewAdminFragmentTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.pm.UserInfo;
+import android.widget.Button;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+
+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;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests for ChooseNewAdminFragment.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserIconProvider.class})
+public class ChooseNewAdminFragmentTest {
+
+    private static final UserInfo TEST_ADMIN_USER = new UserInfo(/* id= */ 10,
+            "TEST_USER_NAME", /* flags= */ 0);
+    private BaseTestActivity mTestActivity;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUpTestActivity() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mTestActivity = Robolectric.setupActivity(BaseTestActivity.class);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testBackButtonPressed_whenRemoveCancelled() {
+        ChooseNewAdminFragment fragment = ChooseNewAdminFragment.newInstance(TEST_ADMIN_USER);
+        mTestActivity.launchFragment(fragment);
+        Button actionButton = (Button) mTestActivity.findViewById(R.id.action_button1);
+
+        actionButton.callOnClick();
+        assertThat(mTestActivity.getOnBackPressedFlag()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/ChooseNewAdminPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/users/ChooseNewAdminPreferenceControllerTest.java
new file mode 100644
index 0000000..e539143
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/ChooseNewAdminPreferenceControllerTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.ErrorDialog;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserIconProvider.class})
+public class ChooseNewAdminPreferenceControllerTest {
+
+    private static final UserInfo TEST_ADMIN_USER = new UserInfo(/* id= */ 10,
+            "TEST_USER_NAME", /* flags= */ 0);
+    private static final UserInfo TEST_OTHER_USER = new UserInfo(/* id= */ 11,
+            "TEST_OTHER_NAME", /* flags= */ 0);
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<ChooseNewAdminPreferenceController> mControllerHelper;
+    private ChooseNewAdminPreferenceController mController;
+    private ConfirmationDialogFragment mDialog;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                ChooseNewAdminPreferenceController.class);
+        mController = mControllerHelper.getController();
+        mController.setAdminInfo(TEST_ADMIN_USER);
+        mControllerHelper.setPreference(new LogicalPreferenceGroup(mContext));
+        mDialog = new ConfirmationDialogFragment.Builder(mContext).build();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testOnCreate_hasPreviousDialog_dialogListenerSet() {
+        when(mControllerHelper.getMockFragmentController().findDialogByTag(
+                ConfirmationDialogFragment.TAG)).thenReturn(mDialog);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mDialog.getConfirmListener()).isNotNull();
+    }
+
+    @Test
+    public void testCheckInitialized_noAdminInfoSet_throwsError() {
+        assertThrows(IllegalStateException.class,
+                () -> new PreferenceControllerTestHelper<>(mContext,
+                        ChooseNewAdminPreferenceController.class,
+                        new LogicalPreferenceGroup(mContext)));
+    }
+
+    @Test
+    public void testUserClicked_opensDialog() {
+        mController.userClicked(/* userToMakeAdmin= */ TEST_OTHER_USER);
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmationDialogFragment.class),
+                eq(ConfirmationDialogFragment.TAG));
+    }
+
+    @Test
+    public void testAssignNewAdminAndRemoveOldAdmin_grantAdminCalled() {
+        mController.assignNewAdminAndRemoveOldAdmin(TEST_OTHER_USER);
+
+        verify(mCarUserManagerHelper).grantAdminPermissions(TEST_OTHER_USER);
+    }
+
+    @Test
+    public void testAssignNewAdminAndRemoveOldAdmin_removeUserCalled() {
+        mController.assignNewAdminAndRemoveOldAdmin(TEST_OTHER_USER);
+
+        verify(mCarUserManagerHelper).removeUser(eq(TEST_ADMIN_USER), anyString());
+    }
+
+    @Test
+    public void testAssignNewAdminAndRemoveOldAdmin_success_noErrorDialog() {
+        when(mCarUserManagerHelper.removeUser(TEST_ADMIN_USER,
+                mContext.getString(R.string.user_guest))).thenReturn(true);
+
+        mController.assignNewAdminAndRemoveOldAdmin(TEST_OTHER_USER);
+
+        verify(mControllerHelper.getMockFragmentController(), never()).showDialog(
+                any(ErrorDialog.class), any());
+    }
+
+    @Test
+    public void testAssignNewAdminAndRemoveOldAdmin_failure_errorDialog() {
+        when(mCarUserManagerHelper.removeUser(TEST_ADMIN_USER,
+                mContext.getString(R.string.user_guest))).thenReturn(false);
+
+        mController.assignNewAdminAndRemoveOldAdmin(TEST_OTHER_USER);
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(any(ErrorDialog.class),
+                any());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/ConfirmExitRetailModeDialogTest.java b/tests/robotests/src/com/android/car/settings/users/ConfirmExitRetailModeDialogTest.java
new file mode 100644
index 0000000..db8ee92
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/ConfirmExitRetailModeDialogTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.DialogTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+
+/**
+ * Tests for ConfirmExitRetailModeDialog.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ConfirmExitRetailModeDialogTest {
+    private BaseTestActivity mTestActivity;
+    private Fragment mTestFragment;
+    private ConfirmExitRetailModeDialog mDialog;
+
+    @Before
+    public void setUpTestActivity() {
+        MockitoAnnotations.initMocks(this);
+
+        mTestActivity = Robolectric.setupActivity(BaseTestActivity.class);
+
+        mTestFragment = new Fragment();
+        mTestActivity.launchFragment(mTestFragment);
+
+        mDialog = new ConfirmExitRetailModeDialog();
+    }
+
+    @Test
+    public void testConfirmExitRetailModeInvokesOnExitRetailModeConfirmed() {
+        ConfirmExitRetailModeDialog.ConfirmExitRetailModeListener listener = Mockito.mock(
+                ConfirmExitRetailModeDialog.ConfirmExitRetailModeListener.class);
+        mDialog.setConfirmExitRetailModeListener(listener);
+        showDialog();
+
+        // Invoke exit retail mode.
+        DialogTestUtils.clickPositiveButton(mDialog);
+
+        verify(listener).onExitRetailModeConfirmed();
+        assertThat(isDialogShown()).isFalse(); // Dialog is dismissed.
+    }
+
+    @Test
+    public void testCancelDismissesDialog() {
+        showDialog();
+
+        // Invoke cancel.
+        DialogTestUtils.clickNegativeButton(mDialog);
+
+        assertThat(isDialogShown()).isFalse(); // Dialog is dismissed.
+    }
+
+    @Test
+    public void testNoConfirmClickListenerDismissesDialog() {
+        showDialog();
+
+        // Invoke confirm add user.
+        DialogTestUtils.clickPositiveButton(mDialog);
+
+        assertThat(isDialogShown()).isFalse(); // Dialog is dismissed.
+    }
+
+    private void showDialog() {
+        mDialog.show(mTestFragment);
+        assertThat(isDialogShown()).isTrue();
+    }
+
+    private boolean isDialogShown() {
+        return mTestActivity.getSupportFragmentManager()
+                .findFragmentByTag(ConfirmExitRetailModeDialog.DIALOG_TAG) != null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/EditUserNameEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/users/EditUserNameEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..c97b47b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/EditUserNameEntryPreferenceControllerTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static android.content.pm.UserInfo.FLAG_ADMIN;
+import static android.content.pm.UserInfo.FLAG_INITIALIZED;
+
+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.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserIconProvider.class})
+public class EditUserNameEntryPreferenceControllerTest {
+
+    private static final String TEST_USERNAME = "Test Username";
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<EditUserNameEntryPreferenceController>
+            mPreferenceControllerHelper;
+    private EditUserNameEntryPreferenceController mController;
+    private ButtonPreference mButtonPreference;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                EditUserNameEntryPreferenceController.class);
+        mController = mPreferenceControllerHelper.getController();
+        mController.setUserInfo(new UserInfo());
+        mButtonPreference = new ButtonPreference(mContext);
+        mButtonPreference.setSelectable(false);
+        mPreferenceControllerHelper.setPreference(mButtonPreference);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testOnButtonClick_isCurrentUser_launchesEditUsernameFragment() {
+        UserInfo userInfo = new UserInfo(/* id= */ 10, TEST_USERNAME, /* flags= */ 0);
+        when(mCarUserManagerHelper.isCurrentProcessUser(userInfo)).thenReturn(true);
+        mController.setUserInfo(userInfo);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mButtonPreference.performButtonClick();
+        verify(mPreferenceControllerHelper.getMockFragmentController()).launchFragment(
+                any(EditUsernameFragment.class));
+    }
+
+    @Test
+    public void testOnButtonClick_isNotCurrentUser_doesNothing() {
+        UserInfo userInfo = new UserInfo(/* id= */ 10, TEST_USERNAME, /* flags= */ 0);
+        when(mCarUserManagerHelper.isCurrentProcessUser(userInfo)).thenReturn(false);
+        mController.setUserInfo(userInfo);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mButtonPreference.performButtonClick();
+        verify(mPreferenceControllerHelper.getMockFragmentController(), never()).launchFragment(
+                any(EditUsernameFragment.class));
+    }
+
+    @Test
+    public void testRefreshUi_elementHasTitle() {
+        UserInfo userInfo = new UserInfo(/* id= */ 10, TEST_USERNAME, /* flags= */ 0);
+        when(mCarUserManagerHelper.isCurrentProcessUser(userInfo)).thenReturn(false);
+        mController.setUserInfo(userInfo);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+        assertThat(mButtonPreference.getTitle()).isEqualTo(TEST_USERNAME);
+    }
+
+    @Test
+    public void testRefreshUi_userNotSetup_setsSummary() {
+        UserInfo userInfo = new UserInfo(/* id= */ 10, TEST_USERNAME, /* flags= */ 0);
+        mController.setUserInfo(userInfo);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+        assertThat(mButtonPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.user_summary_not_set_up));
+    }
+
+    @Test
+    public void testRefreshUi_userSetup_noSummary() {
+        UserInfo userInfo = new UserInfo(/* id= */ 10, TEST_USERNAME, FLAG_INITIALIZED);
+        mController.setUserInfo(userInfo);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+        assertThat(mButtonPreference.getSummary()).isNull();
+    }
+
+    @Test
+    public void testRefreshUi_isAdmin_notCurrentUser() {
+        UserInfo userInfo = new UserInfo(/* id= */ 10, TEST_USERNAME,
+                FLAG_INITIALIZED | FLAG_ADMIN);
+        when(mCarUserManagerHelper.isCurrentProcessUser(userInfo)).thenReturn(false);
+        mController.setUserInfo(userInfo);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+        assertThat(mButtonPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.user_admin));
+    }
+
+    @Test
+    public void testRefreshUi_isAdmin_currentUser() {
+        UserInfo userInfo = new UserInfo(/* id= */ 10, TEST_USERNAME,
+                FLAG_INITIALIZED | FLAG_ADMIN);
+        when(mCarUserManagerHelper.isCurrentProcessUser(userInfo)).thenReturn(true);
+        mController.setUserInfo(userInfo);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.refreshUi();
+        assertThat(mButtonPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.signed_in_admin_user));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/EditUsernameFragmentTest.java b/tests/robotests/src/com/android/car/settings/users/EditUsernameFragmentTest.java
index b441b8f..d4b5061 100644
--- a/tests/robotests/src/com/android/car/settings/users/EditUsernameFragmentTest.java
+++ b/tests/robotests/src/com/android/car/settings/users/EditUsernameFragmentTest.java
@@ -21,17 +21,18 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
 import android.content.pm.UserInfo;
 import android.os.UserManager;
-import android.support.design.widget.TextInputEditText;
 import android.widget.Button;
+import android.widget.EditText;
 
 import com.android.car.settings.CarSettingsRobolectricTestRunner;
 import com.android.car.settings.R;
 import com.android.car.settings.testutils.BaseTestActivity;
 import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,7 +45,7 @@
  * Tests for EditUsernameFragment.
  */
 @RunWith(CarSettingsRobolectricTestRunner.class)
-@Config(shadows = { ShadowCarUserManagerHelper.class })
+@Config(shadows = {ShadowCarUserManagerHelper.class})
 public class EditUsernameFragmentTest {
     private BaseTestActivity mTestActivity;
 
@@ -57,9 +58,12 @@
     public void setUpTestActivity() {
         MockitoAnnotations.initMocks(this);
         ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
-        mTestActivity = Robolectric.buildActivity(BaseTestActivity.class)
-                .setup()
-                .get();
+        mTestActivity = Robolectric.setupActivity(BaseTestActivity.class);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
     }
 
     /**
@@ -71,8 +75,7 @@
         UserInfo testUser = new UserInfo(/* id= */ 10, testUserName, /* flags= */ 0);
         createEditUsernameFragment(testUser);
 
-        TextInputEditText userNameEditText =
-                (TextInputEditText) mTestActivity.findViewById(R.id.user_name_text_edit);
+        EditText userNameEditText = mTestActivity.findViewById(R.id.user_name_text_edit);
         assertThat(userNameEditText.getText().toString()).isEqualTo(testUserName);
     }
 
@@ -83,13 +86,13 @@
     public void testClickingOkSavesNewUserName() {
         UserInfo testUser = new UserInfo(/* id= */ 10, "user_name", /* flags= */ 0);
         createEditUsernameFragment(testUser);
-        TextInputEditText userNameEditText =
-                (TextInputEditText) mTestActivity.findViewById(R.id.user_name_text_edit);
+        EditText userNameEditText = mTestActivity.findViewById(R.id.user_name_text_edit);
         Button okButton = (Button) mTestActivity.findViewById(R.id.action_button2);
 
         String newUserName = "new_user_name";
         userNameEditText.requestFocus();
         userNameEditText.setText(newUserName);
+        assertThat(okButton.isEnabled()).isTrue();
         okButton.callOnClick();
 
         verify(mCarUserManagerHelper).setUserName(testUser, newUserName);
@@ -103,8 +106,7 @@
         int userId = 10;
         UserInfo testUser = new UserInfo(userId, /* name= */ "test_user", /* flags= */ 0);
         createEditUsernameFragment(testUser);
-        TextInputEditText userNameEditText =
-                (TextInputEditText) mTestActivity.findViewById(R.id.user_name_text_edit);
+        EditText userNameEditText = mTestActivity.findViewById(R.id.user_name_text_edit);
         Button cancelButton = (Button) mTestActivity.findViewById(R.id.action_button1);
 
         String newUserName = "new_user_name";
@@ -121,6 +123,19 @@
         verify(mUserManager, never()).setUserName(userId, newUserName);
     }
 
+    @Test
+    public void testEmptyUsernameCannotBeSaved() {
+        UserInfo testUser = new UserInfo(/* id= */ 10, "user_name", /* flags= */ 0);
+        createEditUsernameFragment(testUser);
+        EditText userNameEditText = mTestActivity.findViewById(R.id.user_name_text_edit);
+        Button okButton = (Button) mTestActivity.findViewById(R.id.action_button2);
+
+        userNameEditText.requestFocus();
+        userNameEditText.setText("");
+
+        assertThat(okButton.isEnabled()).isFalse();
+    }
+
     private void createEditUsernameFragment(UserInfo userInfo) {
         EditUsernameFragment fragment = EditUsernameFragment.newInstance(userInfo);
         mTestActivity.launchFragment(fragment);
diff --git a/tests/robotests/src/com/android/car/settings/users/MakeAdminPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/users/MakeAdminPreferenceControllerTest.java
new file mode 100644
index 0000000..5c62bcd
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/MakeAdminPreferenceControllerTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.ButtonPreference;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserIconProvider.class})
+public class MakeAdminPreferenceControllerTest {
+
+    private static final UserInfo TEST_USER = new UserInfo(/* id= */ 10,
+            "Test Username", /* flags= */0);
+
+    private PreferenceControllerTestHelper<MakeAdminPreferenceController>
+            mPreferenceControllerHelper;
+    private MakeAdminPreferenceController mController;
+    private ButtonPreference mButtonPreference;
+    private ConfirmationDialogFragment mDialog;
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        Context context = RuntimeEnvironment.application;
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
+                MakeAdminPreferenceController.class);
+        mController = mPreferenceControllerHelper.getController();
+        mController.setUserInfo(TEST_USER);
+        mButtonPreference = new ButtonPreference(context);
+        mButtonPreference.setSelectable(false);
+        mPreferenceControllerHelper.setPreference(mButtonPreference);
+        mDialog = new ConfirmationDialogFragment.Builder(context).build();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testOnCreate_hasPreviousDialog_dialogListenerSet() {
+        when(mPreferenceControllerHelper.getMockFragmentController().findDialogByTag(
+                ConfirmationDialogFragment.TAG)).thenReturn(mDialog);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mDialog.getConfirmListener()).isNotNull();
+    }
+
+    @Test
+    public void testOnButtonClick_showsDialog() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mButtonPreference.performButtonClick();
+        verify(mPreferenceControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmationDialogFragment.class),
+                matches(ConfirmationDialogFragment.TAG));
+    }
+
+    @Test
+    public void testListener_makeUserAdmin() {
+        Bundle arguments = new Bundle();
+        arguments.putParcelable(UsersDialogProvider.KEY_USER_TO_MAKE_ADMIN, TEST_USER);
+        mController.mConfirmListener.onConfirm(arguments);
+        verify(mCarUserManagerHelper).grantAdminPermissions(TEST_USER);
+    }
+
+    @Test
+    public void testListener_goBack() {
+        Bundle arguments = new Bundle();
+        arguments.putParcelable(UsersDialogProvider.KEY_USER_TO_MAKE_ADMIN, TEST_USER);
+        mController.mConfirmListener.onConfirm(arguments);
+        verify(mPreferenceControllerHelper.getMockFragmentController()).goBack();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/PermissionsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/users/PermissionsPreferenceControllerTest.java
new file mode 100644
index 0000000..9245f97
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/PermissionsPreferenceControllerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/**
+ * Test for the preference controller which populates the various permissions preferences.
+ * Note that the switch preference represents the opposite of the restriction it is controlling.
+ * i.e. DISALLOW_ADD_USER may be the restriction, but the switch represents "create new users".
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class PermissionsPreferenceControllerTest {
+
+    private static final String TEST_RESTRICTION = UserManager.DISALLOW_ADD_USER;
+    private static final UserInfo TEST_USER = new UserInfo(/* id= */ 10,
+            "TEST_USER_NAME", /* flags= */ 0);
+
+    private PreferenceControllerTestHelper<PermissionsPreferenceController>
+            mPreferenceControllerHelper;
+    private PermissionsPreferenceController mController;
+    private PreferenceGroup mPreferenceGroup;
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        Context context = RuntimeEnvironment.application;
+        ShadowCarUserManagerHelper.setMockInstance(mock(CarUserManagerHelper.class));
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
+                PermissionsPreferenceController.class);
+        mController = mPreferenceControllerHelper.getController();
+        mController.setUserInfo(TEST_USER);
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mPreferenceControllerHelper.setPreference(mPreferenceGroup);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testRefreshUi_populatesGroup() {
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(5);
+    }
+
+    @Test
+    public void testRefreshUi_callingTwice_noDuplicates() {
+        mController.refreshUi();
+        mController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(5);
+    }
+
+    @Test
+    public void testRefreshUi_setToFalse() {
+        SwitchPreference preference = getPreferenceForRestriction(mPreferenceGroup,
+                TEST_RESTRICTION);
+        preference.setChecked(true);
+        mCarUserManagerHelper.setUserRestriction(TEST_USER, TEST_RESTRICTION, true);
+        mController.refreshUi();
+        assertThat(preference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void testRefreshUi_setToTrue() {
+        SwitchPreference preference = getPreferenceForRestriction(mPreferenceGroup,
+                TEST_RESTRICTION);
+        preference.setChecked(false);
+        mCarUserManagerHelper.setUserRestriction(TEST_USER, TEST_RESTRICTION, false);
+        mController.refreshUi();
+        assertThat(preference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void testOnPreferenceChange_changeToFalse() {
+        SwitchPreference preference = getPreferenceForRestriction(mPreferenceGroup,
+                TEST_RESTRICTION);
+        mCarUserManagerHelper.setUserRestriction(TEST_USER, TEST_RESTRICTION, true);
+        preference.callChangeListener(true);
+        assertThat(mCarUserManagerHelper.hasUserRestriction(TEST_RESTRICTION, TEST_USER)).isFalse();
+    }
+
+    @Test
+    public void testOnPreferenceChange_changeToTrue() {
+        SwitchPreference preference = getPreferenceForRestriction(mPreferenceGroup,
+                TEST_RESTRICTION);
+        mCarUserManagerHelper.setUserRestriction(TEST_USER, TEST_RESTRICTION, false);
+        preference.callChangeListener(false);
+        assertThat(mCarUserManagerHelper.hasUserRestriction(TEST_RESTRICTION, TEST_USER)).isTrue();
+    }
+
+    private SwitchPreference getPreferenceForRestriction(
+            PreferenceGroup preferenceGroup, String restriction) {
+        for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
+            SwitchPreference preference = (SwitchPreference) preferenceGroup.getPreference(i);
+            if (restriction.equals(preference.getExtras().getString(
+                    PermissionsPreferenceController.PERMISSION_TYPE_KEY))) {
+                return preference;
+            }
+        }
+        return null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/UserDetailsBaseFragmentTest.java b/tests/robotests/src/com/android/car/settings/users/UserDetailsBaseFragmentTest.java
new file mode 100644
index 0000000..6aaed03
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/UserDetailsBaseFragmentTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+import com.android.car.settings.testutils.ShadowUserManager;
+
+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;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowUserManager.class, ShadowCarUserManagerHelper.class,
+        ShadowUserIconProvider.class})
+public class UserDetailsBaseFragmentTest {
+
+    /*
+     * This class needs to be public and static in order for it to be recreated from instance
+     * state if necessary.
+     */
+    public static class TestUserDetailsBaseFragment extends UserDetailsBaseFragment {
+
+        @Override
+        protected String getTitleText() {
+            return "test_title";
+        }
+
+        @Override
+        protected int getPreferenceScreenResId() {
+            return R.xml.test_user_details_base_fragment;
+        }
+    }
+
+    private BaseTestActivity mTestActivity;
+    private UserDetailsBaseFragment mUserDetailsBaseFragment;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private UserManager mUserManager;
+
+    private Button mRemoveUserButton;
+
+    @Before
+    public void setUpTestActivity() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        ShadowUserManager.setInstance(mUserManager);
+
+        mTestActivity = Robolectric.setupActivity(BaseTestActivity.class);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowUserManager.reset();
+    }
+
+    @Test
+    public void testRemoveUserButtonVisible_whenAllowedToRemoveUsers() {
+        when(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).thenReturn(true);
+        when(mCarUserManagerHelper.canUserBeRemoved(any())).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+        createUserDetailsBaseFragment();
+
+        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testRemoveUserButtonHidden_whenNotAllowedToRemoveUSers() {
+        when(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).thenReturn(false);
+        when(mCarUserManagerHelper.canUserBeRemoved(any())).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+        createUserDetailsBaseFragment();
+
+        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testRemoveUserButtonHidden_whenUserCannotBeRemoved() {
+        when(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).thenReturn(true);
+        when(mCarUserManagerHelper.canUserBeRemoved(any())).thenReturn(false);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+        createUserDetailsBaseFragment();
+
+        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testRemoveUserButtonHidden_demoUser() {
+        when(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).thenReturn(true);
+        when(mCarUserManagerHelper.canUserBeRemoved(any())).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(true);
+        createUserDetailsBaseFragment();
+
+        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testRemoveUserButtonClick_createsRemovalDialog() {
+        when(mCarUserManagerHelper.canCurrentProcessRemoveUsers()).thenReturn(true);
+        when(mCarUserManagerHelper.canUserBeRemoved(any())).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessDemoUser()).thenReturn(false);
+        when(mCarUserManagerHelper.getAllPersistentUsers()).thenReturn(
+                Arrays.asList(new UserInfo()));
+        createUserDetailsBaseFragment();
+        mRemoveUserButton.performClick();
+
+        assertThat(mUserDetailsBaseFragment.findDialogByTag(
+                ConfirmationDialogFragment.TAG)).isNotNull();
+    }
+
+    private void createUserDetailsBaseFragment() {
+        UserInfo testUser = new UserInfo();
+        // Use UserDetailsFragment, since we cannot test an abstract class.
+        mUserDetailsBaseFragment = UserDetailsBaseFragment.addUserIdToFragmentArguments(
+                new TestUserDetailsBaseFragment(), testUser.id);
+        when(mUserManager.getUserInfo(testUser.id)).thenReturn(testUser);
+        mTestActivity.launchFragment(mUserDetailsBaseFragment);
+        mRemoveUserButton = (Button) mTestActivity.findViewById(R.id.action_button1);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/UserDetailsBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/users/UserDetailsBasePreferenceControllerTest.java
new file mode 100644
index 0000000..97a988f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/UserDetailsBasePreferenceControllerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserIconProvider.class})
+public class UserDetailsBasePreferenceControllerTest {
+
+    private static class TestUserDetailsBasePreferenceController extends
+            UserDetailsBasePreferenceController<Preference> {
+
+        TestUserDetailsBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected Class<Preference> getPreferenceType() {
+            return Preference.class;
+        }
+    }
+
+    private PreferenceControllerTestHelper<TestUserDetailsBasePreferenceController>
+            mPreferenceControllerHelper;
+    private TestUserDetailsBasePreferenceController mController;
+    private Preference mPreference;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        Context context = RuntimeEnvironment.application;
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
+                TestUserDetailsBasePreferenceController.class);
+        mController = mPreferenceControllerHelper.getController();
+        mPreference = new Preference(context);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testCheckInitialized_missingUserInfo() {
+        assertThrows(() -> mPreferenceControllerHelper.setPreference(mPreference));
+    }
+
+    @Test
+    public void testOnCreate_registerListener() {
+        mController.setUserInfo(new UserInfo());
+        mPreferenceControllerHelper.setPreference(mPreference);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        verify(mCarUserManagerHelper).registerOnUsersUpdateListener(any(CarUserManagerHelper
+                .OnUsersUpdateListener.class));
+    }
+
+    @Test
+    public void testOnDestroy_unregisterListener() {
+        mController.setUserInfo(new UserInfo());
+        mPreferenceControllerHelper.setPreference(mPreference);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        ArgumentCaptor<CarUserManagerHelper.OnUsersUpdateListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(CarUserManagerHelper.OnUsersUpdateListener.class);
+        verify(mCarUserManagerHelper).registerOnUsersUpdateListener(
+                listenerArgumentCaptor.capture());
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+        verify(mCarUserManagerHelper).unregisterOnUsersUpdateListener(
+                listenerArgumentCaptor.getValue());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/UserDetailsFragmentTest.java b/tests/robotests/src/com/android/car/settings/users/UserDetailsFragmentTest.java
index 84daa8e..c6b51b2 100644
--- a/tests/robotests/src/com/android/car/settings/users/UserDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/car/settings/users/UserDetailsFragmentTest.java
@@ -18,164 +18,97 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
-import android.car.user.CarUserManagerHelper;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
 import android.content.pm.UserInfo;
-import android.view.View;
-import android.widget.Button;
+import android.os.UserManager;
+import android.widget.TextView;
 
 import com.android.car.settings.CarSettingsRobolectricTestRunner;
 import com.android.car.settings.R;
 import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
 import com.android.car.settings.testutils.ShadowUserIconProvider;
+import com.android.car.settings.testutils.ShadowUserManager;
 
+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;
 import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
-/**
- * Tests for UserDetailsFragment.
- */
 @RunWith(CarSettingsRobolectricTestRunner.class)
-@Config(shadows = { ShadowUserIconProvider.class })
+@Config(shadows = {ShadowUserManager.class, ShadowCarUserManagerHelper.class,
+        ShadowUserIconProvider.class})
 public class UserDetailsFragmentTest {
+
+    private static final String TEST_NAME = "test_name";
+    private static final String TEST_UPDATED_NAME = "test_updated_name";
+    private static final int TEST_USER_ID = 10;
+
+    private Context mContext;
     private BaseTestActivity mTestActivity;
     private UserDetailsFragment mUserDetailsFragment;
-
     @Mock
     private CarUserManagerHelper mCarUserManagerHelper;
     @Mock
-    private UserIconProvider mUserIconProvider;
+    private UserManager mUserManager;
 
-    private Button mRemoveUserButton;
-    private Button mSwitchUserButton;
+    private TextView mTitle;
 
     @Before
     public void setUpTestActivity() {
         MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        ShadowUserManager.setInstance(mUserManager);
 
-        when(mUserIconProvider.getUserIcon(any(), any())).thenReturn(null);
-
-        mTestActivity = Robolectric.buildActivity(BaseTestActivity.class)
-                .setup()
-                .get();
+        mContext = RuntimeEnvironment.application;
+        mTestActivity = Robolectric.setupActivity(BaseTestActivity.class);
     }
 
-    /**
-     * Tests that if the current user cannot remove other users, the removeUserButton is hidden.
-     */
-    @Test
-    public void test_canCurrentProcessRemoveUsers_isFalse_hidesRemoveUserButton() {
-        doReturn(true).when(mCarUserManagerHelper).canCurrentProcessRemoveUsers();
-        doReturn(true).when(mCarUserManagerHelper).canUserBeRemoved(any());
-        doReturn(false).when(mCarUserManagerHelper).isCurrentProcessDemoUser();
-        createUserDetailsFragment();
-
-        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.VISIBLE);
-
-        doReturn(false).when(mCarUserManagerHelper).canCurrentProcessRemoveUsers();
-        refreshFragment();
-
-        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.GONE);
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+        ShadowUserManager.reset();
     }
 
-    /**
-     * Tests that if the user cannot be removed, the remove button is hidden.
-     */
     @Test
-    public void test_canUserBeRemoved_isFalse_hidesRemoveUserButton() {
-        doReturn(true).when(mCarUserManagerHelper).canCurrentProcessRemoveUsers();
-        doReturn(true).when(mCarUserManagerHelper).canUserBeRemoved(any());
-        doReturn(false).when(mCarUserManagerHelper).isCurrentProcessDemoUser();
+    public void testCarUserManagerHelperUpdateListener_showsCorrectText() {
+        UserInfo testUser = new UserInfo(TEST_USER_ID, TEST_NAME, /* flags= */ 0);
+        when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(testUser);
         createUserDetailsFragment();
-
-        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.VISIBLE);
-
-        doReturn(false).when(mCarUserManagerHelper).canUserBeRemoved(any());
-        refreshFragment();
-
-        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.GONE);
+        mUserDetailsFragment.mOnUsersUpdateListener.onUsersUpdate();
+        assertThat(mTitle.getText()).isEqualTo(
+                UserUtils.getUserDisplayName(mContext, mCarUserManagerHelper, testUser));
     }
 
-    /**
-     * Tests that demo user cannot remove other users.
-     */
     @Test
-    public void test_isCurrentProcessDemoUser_isTrue_hidesRemoveUserButton() {
-        doReturn(true).when(mCarUserManagerHelper).canCurrentProcessRemoveUsers();
-        doReturn(true).when(mCarUserManagerHelper).canUserBeRemoved(any());
-        doReturn(false).when(mCarUserManagerHelper).isCurrentProcessDemoUser();
+    public void testCarUserManagerHelperUpdateListener_textChangesWithUserUpdate() {
+        UserInfo testUser = new UserInfo(TEST_USER_ID, TEST_NAME, /* flags= */ 0);
+        when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(testUser);
+
         createUserDetailsFragment();
+        mUserDetailsFragment.mOnUsersUpdateListener.onUsersUpdate();
+        assertThat(mTitle.getText()).isEqualTo(
+                UserUtils.getUserDisplayName(mContext, mCarUserManagerHelper, testUser));
 
-        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.VISIBLE);
+        UserInfo testUserUpdated = new UserInfo(TEST_USER_ID, TEST_UPDATED_NAME, /* flags= */ 0);
+        when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(testUserUpdated);
 
-        doReturn(true).when(mCarUserManagerHelper).isCurrentProcessDemoUser();
-        refreshFragment();
-
-        assertThat(mRemoveUserButton.getVisibility()).isEqualTo(View.GONE);
-    }
-
-    /**
-     * Tests that if the current user cannot switch to other users, the switchUserButton is hidden.
-     */
-    @Test
-    public void test_canCurrentProcessSwitchUsers_isFalse_hidesSwitchUserButton() {
-        doReturn(true).when(mCarUserManagerHelper).canCurrentProcessSwitchUsers();
-        doReturn(false).when(mCarUserManagerHelper).isForegroundUser(any());
-        createUserDetailsFragment();
-
-        assertThat(mSwitchUserButton.getVisibility()).isEqualTo(View.VISIBLE);
-
-        doReturn(false).when(mCarUserManagerHelper).canCurrentProcessSwitchUsers();
-        refreshFragment();
-
-        assertThat(mSwitchUserButton.getVisibility()).isEqualTo(View.GONE);
-    }
-
-    /**
-     * Tests that if UserDetailsFragment is displaying foreground user already, switch button is
-     * hidden.
-     */
-    @Test
-    public void test_isForegroundUser_isTrue_hidesSwitchUserButton() {
-        doReturn(true).when(mCarUserManagerHelper).canCurrentProcessSwitchUsers();
-        doReturn(false).when(mCarUserManagerHelper).isForegroundUser(any());
-        createUserDetailsFragment();
-
-        assertThat(mSwitchUserButton.getVisibility()).isEqualTo(View.VISIBLE);
-
-        doReturn(false).when(mCarUserManagerHelper).canCurrentProcessSwitchUsers();
-        refreshFragment();
-
-        assertThat(mSwitchUserButton.getVisibility()).isEqualTo(View.GONE);
+        mUserDetailsFragment.mOnUsersUpdateListener.onUsersUpdate();
+        assertThat(mTitle.getText()).isEqualTo(
+                UserUtils.getUserDisplayName(mContext, mCarUserManagerHelper, testUserUpdated));
     }
 
     private void createUserDetailsFragment() {
-        UserInfo testUser = new UserInfo();
-
-        mUserDetailsFragment = UserDetailsFragment.newInstance(testUser);
-        mUserDetailsFragment.mCarUserManagerHelper = mCarUserManagerHelper;
+        mUserDetailsFragment = UserDetailsFragment.newInstance(TEST_USER_ID);
         mTestActivity.launchFragment(mUserDetailsFragment);
-
-        refreshButtons();
-    }
-
-    private void refreshFragment() {
-        mTestActivity.reattachFragment(mUserDetailsFragment);
-
-        refreshButtons();
-    }
-
-    private void refreshButtons() {
-        // Get buttons;
-        mRemoveUserButton = (Button) mTestActivity.findViewById(R.id.action_button1);
-        mSwitchUserButton = (Button) mTestActivity.findViewById(R.id.action_button2);
+        mTitle = mTestActivity.findViewById(R.id.title);
     }
 }
diff --git a/tests/robotests/src/com/android/car/settings/users/UsersBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/users/UsersBasePreferenceControllerTest.java
new file mode 100644
index 0000000..42e2b89
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/UsersBasePreferenceControllerTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static android.content.pm.UserInfo.FLAG_ADMIN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserIconProvider.class})
+public class UsersBasePreferenceControllerTest {
+
+    private static class TestUsersBasePreferenceController extends UsersBasePreferenceController {
+
+        TestUsersBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected void userClicked(UserInfo userInfo) {
+        }
+    }
+
+    private static final UserInfo TEST_CURRENT_USER = new UserInfo(/* id= */ 10,
+            "TEST_USER_NAME", /* flags= */ 0);
+    private static final UserInfo TEST_OTHER_USER = new UserInfo(/* id= */ 11,
+            "TEST_OTHER_NAME", /* flags= */ 0);
+    private PreferenceControllerTestHelper<TestUsersBasePreferenceController> mControllerHelper;
+    private TestUsersBasePreferenceController mController;
+    private PreferenceGroup mPreferenceGroup;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        Context context = RuntimeEnvironment.application;
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                TestUsersBasePreferenceController.class, mPreferenceGroup);
+        mController = mControllerHelper.getController();
+        when(mCarUserManagerHelper.getCurrentProcessUserInfo()).thenReturn(TEST_CURRENT_USER);
+        when(mCarUserManagerHelper.isCurrentProcessUser(TEST_CURRENT_USER)).thenReturn(true);
+        when(mCarUserManagerHelper.getAllSwitchableUsers()).thenReturn(
+                Collections.singletonList(TEST_OTHER_USER));
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void onCreate_registersOnUsersUpdateListener() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        verify(mCarUserManagerHelper).registerOnUsersUpdateListener(
+                any(CarUserManagerHelper.OnUsersUpdateListener.class));
+    }
+
+    @Test
+    public void onCreate_populatesUsers() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        // Three users. Current user, other user, guest user.
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(3);
+    }
+
+    @Test
+    public void onDestroy_unregistersOnUsersUpdateListener() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        verify(mCarUserManagerHelper).unregisterOnUsersUpdateListener(
+                any(CarUserManagerHelper.OnUsersUpdateListener.class));
+    }
+
+    @Test
+    public void refreshUi_userChange_updatesGroup() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        // Store the list of previous Preferences.
+        List<Preference> currentPreferences = new ArrayList<>();
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            currentPreferences.add(mPreferenceGroup.getPreference(i));
+        }
+
+        // Mock a change so that other user becomes an admin.
+        UserInfo adminOtherUser = new UserInfo(/* id= */ 11, "TEST_OTHER_NAME", FLAG_ADMIN);
+        when(mCarUserManagerHelper.getAllSwitchableUsers()).thenReturn(
+                Collections.singletonList(adminOtherUser));
+
+        mController.refreshUi();
+
+        List<Preference> newPreferences = new ArrayList<>();
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            newPreferences.add(mPreferenceGroup.getPreference(i));
+        }
+
+        assertThat(newPreferences).containsNoneIn(currentPreferences);
+    }
+
+    @Test
+    public void refreshUi_noChange_doesNotUpdateGroup() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+
+        // Store the list of previous Preferences.
+        List<Preference> currentPreferences = new ArrayList<>();
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            currentPreferences.add(mPreferenceGroup.getPreference(i));
+        }
+
+        mController.refreshUi();
+
+        List<Preference> newPreferences = new ArrayList<>();
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            newPreferences.add(mPreferenceGroup.getPreference(i));
+        }
+
+        assertThat(newPreferences).containsExactlyElementsIn(currentPreferences);
+    }
+
+    @Test
+    public void onUsersUpdated_updatesGroup() {
+        ArgumentCaptor<CarUserManagerHelper.OnUsersUpdateListener> listenerCaptor =
+                ArgumentCaptor.forClass(CarUserManagerHelper.OnUsersUpdateListener.class);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        verify(mCarUserManagerHelper).registerOnUsersUpdateListener(listenerCaptor.capture());
+
+        // Store the list of previous Preferences.
+        List<Preference> currentPreferences = new ArrayList<>();
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            currentPreferences.add(mPreferenceGroup.getPreference(i));
+        }
+
+        // Mock a change so that other user becomes an admin.
+        UserInfo adminOtherUser = new UserInfo(/* id= */ 11, "TEST_OTHER_NAME", FLAG_ADMIN);
+        when(mCarUserManagerHelper.getAllSwitchableUsers()).thenReturn(
+                Collections.singletonList(adminOtherUser));
+
+        listenerCaptor.getValue().onUsersUpdate();
+
+        List<Preference> newPreferences = new ArrayList<>();
+        for (int i = 0; i < mPreferenceGroup.getPreferenceCount(); i++) {
+            newPreferences.add(mPreferenceGroup.getPreference(i));
+        }
+
+        assertThat(newPreferences).containsNoneIn(currentPreferences);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/UsersEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/users/UsersEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..e9744c5
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/UsersEntryPreferenceControllerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link UsersEntryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class})
+public class UsersEntryPreferenceControllerTest {
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    private Preference mPreference;
+    private PreferenceControllerTestHelper<UsersEntryPreferenceController> mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+
+        Context context = RuntimeEnvironment.application;
+        mPreference = new Preference(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                UsersEntryPreferenceController.class, mPreference);
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void preferenceClicked_adminUser_handled() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+
+        assertThat(
+                mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference)).isTrue();
+    }
+
+    @Test
+    public void preferenceClicked_adminUser_launchesUsersListFragment() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+
+        mPreference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).launchFragment(
+                any(UsersListFragment.class));
+    }
+
+    @Test
+    public void preferenceClicked_nonAdminUser_handled() {
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+
+        assertThat(
+                mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference)).isTrue();
+    }
+
+    @Test
+    public void preferenceClicked_nonAdminUser_launchesUserDetailsFragment() {
+        int userId = 1234;
+        when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(userId);
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(false);
+
+        mPreference.performClick();
+
+        ArgumentCaptor<UserDetailsFragment> fragmentCaptor = ArgumentCaptor.forClass(
+                UserDetailsFragment.class);
+        verify(mControllerHelper.getMockFragmentController()).launchFragment(
+                fragmentCaptor.capture());
+        UserDetailsFragment launchedFragment = fragmentCaptor.getValue();
+        assertThat(launchedFragment.getArguments()).isNotNull();
+        assertThat(launchedFragment.getArguments().getInt(Intent.EXTRA_USER_ID)).isEqualTo(userId);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/UsersItemProviderTest.java b/tests/robotests/src/com/android/car/settings/users/UsersItemProviderTest.java
deleted file mode 100644
index a563846..0000000
--- a/tests/robotests/src/com/android/car/settings/users/UsersItemProviderTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2018 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.car.settings.users;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.robolectric.RuntimeEnvironment.application;
-
-import android.car.user.CarUserManagerHelper;
-import android.content.pm.UserInfo;
-import android.view.View;
-
-import com.android.car.settings.CarSettingsRobolectricTestRunner;
-import com.android.car.settings.R;
-import com.android.car.settings.testutils.ShadowTextListItem;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Tests for UsersItemProviderTest.
- */
-@RunWith(CarSettingsRobolectricTestRunner.class)
-@Config(shadows = { ShadowTextListItem.class })
-public class UsersItemProviderTest {
-    @Mock
-    private CarUserManagerHelper mCarUserManagerHelper;
-    @Mock
-    private UsersItemProvider.UserClickListener mUserClickListener;
-
-    @Before
-    public void setUpMocks() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testFirstUserIsCurrentUser() {
-        UserInfo currentUser = new UserInfo();
-        String testName = "test_user_name";
-        currentUser.name = testName;
-        doReturn(currentUser).when(mCarUserManagerHelper).getCurrentForegroundUserInfo();
-
-        UsersItemProvider usersItemProvider = createProvider();
-
-        ShadowTextListItem textListItem = Shadow.extract(usersItemProvider.get(0));
-        assertThat(textListItem.getTitle())
-                .isEqualTo(application.getString(R.string.current_user_name, testName));
-    }
-
-    @Test
-    public void testDemoUserOnlySeesItself() {
-        UserInfo demoUser = new UserInfo(/* id= */ 10, /* name= */ "Demo", UserInfo.FLAG_DEMO);
-        doReturn(demoUser).when(mCarUserManagerHelper).getCurrentForegroundUserInfo();
-        doReturn(Arrays.asList(new UserInfo(), new UserInfo()))
-                .when(mCarUserManagerHelper).getAllSwitchableUsers();
-
-        UsersItemProvider usersItemProvider = createProvider();
-
-        assertThat(usersItemProvider.size()).isEqualTo(1);
-    }
-
-    @Test
-    public void testGuestUsersNotShown() {
-        UserInfo user10 = new UserInfo(/* id= */ 10, "User 10", /* flags= */ 0);
-        UserInfo user11 = new UserInfo(/* id= */ 11, "User 11", UserInfo.FLAG_GUEST);
-        UserInfo user12 = new UserInfo(/* id= */ 12, "User 12", UserInfo.FLAG_GUEST);
-        UserInfo user13 = new UserInfo(/* id= */ 13, "User 13", /* flags= */ 0);
-        UserInfo user14 = new UserInfo(/* id= */ 14, "User 14", /* flags= */ 0);
-        List<UserInfo> users = Arrays.asList(user10, user11, user12, user13);
-
-        doReturn(user14).when(mCarUserManagerHelper).getCurrentForegroundUserInfo();
-        doReturn(users).when(mCarUserManagerHelper).getAllSwitchableUsers();
-
-        UsersItemProvider provider = createProvider();
-
-        assertThat(getItem(provider, 0).getTitle())
-                .isEqualTo(application.getString(R.string.current_user_name, user14.name));
-        assertThat(getItem(provider, 1).getTitle()).isEqualTo(user10.name);
-        assertThat(getItem(provider, 2).getTitle()).isEqualTo(user13.name);
-    }
-
-    @Test
-    public void testGuestShownAsSeparateItem() {
-        UserInfo user10 = new UserInfo(/* id= */ 10, "User 10", /* flags= */ 0);
-        UserInfo user11 = new UserInfo(/* id= */ 11, "User 11", /* flags= */ UserInfo.FLAG_GUEST);
-        UserInfo user12 = new UserInfo(/* id= */ 12, "User 12", /* flags= */ UserInfo.FLAG_GUEST);
-        UserInfo user13 = new UserInfo(/* id= */ 13, "User 13", /* flags= */ 0);
-        List<UserInfo> users = Arrays.asList(user10, user11, user12, user13);
-
-        doReturn(new UserInfo(/* id= */ 14, "User 14", /* flags= */ 0))
-                .when(mCarUserManagerHelper).getCurrentForegroundUserInfo();
-        doReturn(users).when(mCarUserManagerHelper).getAllSwitchableUsers();
-
-        UsersItemProvider provider = createProvider();
-
-        assertThat(getItem(provider, 3).getTitle())
-                .isEqualTo(application.getString(R.string.user_guest));
-    }
-
-    @Test
-    public void testClickOnUsersInvokesOnUserClicked() {
-        UserInfo currentUser = new UserInfo(/* id= */ 11, "User 11", /* flags= */ 0);
-        List<UserInfo> otherUsers = Arrays.asList(
-                new UserInfo(/* id= */ 10, "User 10", /* flags= */ 0));
-
-        doReturn(currentUser).when(mCarUserManagerHelper).getCurrentForegroundUserInfo();
-        doReturn(otherUsers).when(mCarUserManagerHelper).getAllSwitchableUsers();
-
-        UsersItemProvider provider = createProvider();
-
-        // Clicking on current user invokes OnUserClicked.
-        ShadowTextListItem textListItem = getItem(provider, 0);
-        textListItem.getOnClickListener().onClick(new View(application.getApplicationContext()));
-        verify(mUserClickListener).onUserClicked(currentUser);
-
-        // Clicking on another user invokes OnUserClicked.
-        textListItem = getItem(provider, 1);
-        textListItem.getOnClickListener().onClick(new View(application.getApplicationContext()));
-        verify(mUserClickListener).onUserClicked(otherUsers.get(0));
-    }
-
-    @Test
-    public void testClickOnGuestInvokesOnGuestClicked() {
-        UserInfo currentUser = new UserInfo(/* id= */ 11, "User 11", /* flags= */ 0);
-        doReturn(currentUser).when(mCarUserManagerHelper).getCurrentForegroundUserInfo();
-
-        UsersItemProvider provider = createProvider();
-
-        // Clicking on guest user invokes OnGuestClicked.
-        ShadowTextListItem guestListItem = getItem(provider, 1);
-        guestListItem.getOnClickListener().onClick(new View(application.getApplicationContext()));
-        verify(mUserClickListener).onGuestClicked();
-    }
-
-    @Test
-    public void testSummariesForAdminUsers() {
-        UserInfo currentUser = new UserInfo(/* id= */ 11, "User 11",
-                UserInfo.FLAG_ADMIN | UserInfo.FLAG_INITIALIZED);
-        List<UserInfo> otherUsers = Arrays.asList(new UserInfo(/* id= */ 10, "User 10",
-                UserInfo.FLAG_ADMIN | UserInfo.FLAG_INITIALIZED));
-
-        doReturn(currentUser).when(mCarUserManagerHelper).getCurrentForegroundUserInfo();
-        doReturn(otherUsers).when(mCarUserManagerHelper).getAllSwitchableUsers();
-
-        UsersItemProvider provider = createProvider();
-
-        assertThat(getItem(provider, 0).getBody())
-                .isEqualTo(application.getString(R.string.signed_in_admin_user));
-        assertThat(getItem(provider, 1).getBody())
-                .isEqualTo(application.getString(R.string.user_admin));
-    }
-
-    @Test
-    public void testSummariesForNonInitializedUsers() {
-        UserInfo currentUser = new UserInfo(/* id= */ 11, "User 11", UserInfo.FLAG_INITIALIZED);
-        List<UserInfo> otherUsers = Arrays.asList(
-                new UserInfo(/* id= */ 10, "User 10", /* flags= */ 0));
-
-        doReturn(currentUser).when(mCarUserManagerHelper).getCurrentForegroundUserInfo();
-        doReturn(otherUsers).when(mCarUserManagerHelper).getAllSwitchableUsers();
-
-        UsersItemProvider provider = createProvider();
-
-        assertThat(getItem(provider, 0).getBody()).isNull();
-        assertThat(getItem(provider, 1).getBody())
-                .isEqualTo(application.getString(R.string.user_summary_not_set_up));
-    }
-
-
-    private UsersItemProvider createProvider() {
-        return new UsersItemProvider(RuntimeEnvironment.application.getApplicationContext(),
-                mUserClickListener, mCarUserManagerHelper);
-    }
-
-    private ShadowTextListItem getItem(UsersItemProvider provider, int index) {
-        return Shadow.extract(provider.get(index));
-    }
-}
diff --git a/tests/robotests/src/com/android/car/settings/users/UsersListFragmentTest.java b/tests/robotests/src/com/android/car/settings/users/UsersListFragmentTest.java
new file mode 100644
index 0000000..09a3871
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/UsersListFragmentTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.widget.Button;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.ConfirmationDialogFragment;
+import com.android.car.settings.testutils.BaseTestActivity;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+
+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;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for UserDetailsFragment.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserIconProvider.class})
+public class UsersListFragmentTest {
+
+    private Context mContext;
+    private BaseTestActivity mTestActivity;
+    private UsersListFragment mFragment;
+    private Button mActionButton;
+    private ConfirmationDialogFragment mDialog;
+
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private UserManager mUserManager;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mContext = RuntimeEnvironment.application;
+        mTestActivity = Robolectric.setupActivity(BaseTestActivity.class);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    /* Test that onCreateNewUserConfirmed invokes a creation of a new non-admin. */
+    @Test
+    public void testOnCreateNewUserConfirmedInvokesCreateNewNonAdminUser() {
+        createUsersListFragment();
+        mFragment.mConfirmListener.onConfirm(/* arguments= */ null);
+        Robolectric.flushBackgroundThreadScheduler();
+        verify(mCarUserManagerHelper)
+                .createNewNonAdminUser(mContext.getString(R.string.user_new_user_name));
+    }
+
+    /* Test that if we're in demo user, click on the button starts exit out of the retail mode. */
+    @Test
+    public void testCallOnClick_demoUser_exitRetailMode() {
+        doReturn(true).when(mCarUserManagerHelper).isCurrentProcessDemoUser();
+        createUsersListFragment();
+        mActionButton.callOnClick();
+        assertThat(isDialogShown(ConfirmExitRetailModeDialog.DIALOG_TAG)).isTrue();
+    }
+
+    /* Test that if the max num of users is reached, click on the button informs user of that. */
+    @Test
+    public void testCallOnClick_userLimitReached_showErrorDialog() {
+        doReturn(5).when(mCarUserManagerHelper).getMaxSupportedRealUsers();
+        doReturn(true).when(mCarUserManagerHelper).isUserLimitReached();
+        createUsersListFragment();
+
+        mActionButton.callOnClick();
+        assertThat(isDialogShown(MaxUsersLimitReachedDialog.DIALOG_TAG)).isTrue();
+    }
+
+    /* Test that if user can add other users, click on the button creates a dialog to confirm. */
+    @Test
+    public void testCallOnClick_showAddUserDialog() {
+        doReturn(true).when(mCarUserManagerHelper).canCurrentProcessAddUsers();
+        createUsersListFragment();
+
+        mActionButton.callOnClick();
+        assertThat(isDialogShown(ConfirmationDialogFragment.TAG)).isTrue();
+    }
+
+    private void createUsersListFragment() {
+        UserInfo testUser = new UserInfo();
+        mFragment = new UsersListFragment();
+        doReturn(testUser).when(mCarUserManagerHelper).getCurrentProcessUserInfo();
+        doReturn(testUser).when(mUserManager).getUserInfo(anyInt());
+        doReturn(new ArrayList<UserInfo>()).when(mCarUserManagerHelper).getAllSwitchableUsers();
+        doReturn(null).when(mCarUserManagerHelper).createNewNonAdminUser(any());
+        mTestActivity.launchFragment(mFragment);
+        refreshButtons();
+    }
+
+    private void refreshButtons() {
+        mActionButton = (Button) mTestActivity.findViewById(R.id.action_button1);
+    }
+
+    private boolean isDialogShown(String tag) {
+        return mTestActivity.getSupportFragmentManager().findFragmentByTag(tag) != null;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/UsersListPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/users/UsersListPreferenceControllerTest.java
new file mode 100644
index 0000000..73b7fba
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/UsersListPreferenceControllerTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static android.content.pm.UserInfo.FLAG_ADMIN;
+
+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.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUserIconProvider;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Collections;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUserIconProvider.class})
+public class UsersListPreferenceControllerTest {
+
+    private static final UserInfo TEST_CURRENT_USER = new UserInfo(/* id= */ 10,
+            "TEST_USER_NAME", FLAG_ADMIN);
+    private static final UserInfo TEST_OTHER_USER = new UserInfo(/* id= */ 11,
+            "TEST_OTHER_NAME", /* flags= */ 0);
+
+    private PreferenceControllerTestHelper<UsersListPreferenceController> mControllerHelper;
+    private PreferenceGroup mPreferenceGroup;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+
+    @Before
+    public void setUp() {
+        Context context = RuntimeEnvironment.application;
+        MockitoAnnotations.initMocks(this);
+        ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                UsersListPreferenceController.class, mPreferenceGroup);
+
+        when(mCarUserManagerHelper.getCurrentProcessUserInfo()).thenReturn(TEST_CURRENT_USER);
+        when(mCarUserManagerHelper.isCurrentProcessUser(TEST_CURRENT_USER)).thenReturn(true);
+        when(mCarUserManagerHelper.isCurrentProcessAdminUser()).thenReturn(true);
+        when(mCarUserManagerHelper.getAllSwitchableUsers()).thenReturn(
+                Collections.singletonList(TEST_OTHER_USER));
+
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUserManagerHelper.reset();
+    }
+
+    @Test
+    public void testPreferencePerformClick_currentAdminUser_openNewFragment() {
+        mPreferenceGroup.getPreference(0).performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).launchFragment(
+                any(UserDetailsFragment.class));
+    }
+
+    @Test
+    public void testPreferencePerformClick_otherNonAdminUser_openNewFragment() {
+        mPreferenceGroup.getPreference(1).performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).launchFragment(
+                any(UserDetailsPermissionsFragment.class));
+    }
+
+    @Test
+    public void testPreferencePerformClick_guestUser_noAction() {
+        mPreferenceGroup.getPreference(2).performClick();
+
+        verify(mControllerHelper.getMockFragmentController(), never()).launchFragment(any());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/users/UsersPreferenceProviderTest.java b/tests/robotests/src/com/android/car/settings/users/UsersPreferenceProviderTest.java
new file mode 100644
index 0000000..b08723b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/users/UsersPreferenceProviderTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2018 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.car.settings.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class UsersPreferenceProviderTest {
+
+    private static final String TEST_CURRENT_USER_NAME = "Current User";
+    private static final String TEST_OTHER_USER_1_NAME = "User 1";
+    private static final String TEST_OTHER_USER_2_NAME = "User 2";
+    private static final String TEST_GUEST_USER_1_NAME = "Guest 1";
+    private static final String TEST_GUEST_USER_2_NAME = "Guest 2";
+
+    private static final UserInfo TEST_CURRENT_USER = new UserInfo(/* id= */ 14,
+            TEST_CURRENT_USER_NAME, /* flags= */ 0);
+    private static final UserInfo TEST_OTHER_USER_1 = new UserInfo(/* id= */ 10,
+            TEST_OTHER_USER_1_NAME, /* flags= */ 0);
+    private static final UserInfo TEST_OTHER_USER_2 = new UserInfo(/* id= */ 11,
+            TEST_OTHER_USER_2_NAME, /* flags= */ 0);
+    private static final UserInfo TEST_GUEST_USER_1 = new UserInfo(/* id= */ 12,
+            TEST_GUEST_USER_1_NAME, /* flags= */ UserInfo.FLAG_GUEST);
+    private static final UserInfo TEST_GUEST_USER_2 = new UserInfo(/* id= */ 13,
+            TEST_GUEST_USER_2_NAME, /* flags= */ UserInfo.FLAG_GUEST);
+
+
+    private Context mContext;
+    @Mock
+    private CarUserManagerHelper mCarUserManagerHelper;
+    @Mock
+    private UsersPreferenceProvider.UserClickListener mUserClickListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+
+        List<UserInfo> users = Arrays.asList(TEST_OTHER_USER_1, TEST_GUEST_USER_1,
+                TEST_GUEST_USER_2,
+                TEST_OTHER_USER_2);
+
+        doReturn(TEST_CURRENT_USER).when(mCarUserManagerHelper).getCurrentProcessUserInfo();
+        doReturn(true).when(mCarUserManagerHelper).isCurrentProcessUser(TEST_CURRENT_USER);
+        doReturn(users).when(mCarUserManagerHelper).getAllSwitchableUsers();
+    }
+
+    @Test
+    public void testCreateUserList_firstUserIsCurrentUser() {
+        UsersPreferenceProvider provider = createProvider();
+
+        Preference first = provider.createUserList().get(0);
+        assertThat(first.getTitle()).isEqualTo(
+                mContext.getString(R.string.current_user_name, TEST_CURRENT_USER_NAME));
+    }
+
+    @Test
+    public void testCreateUserList_repeatedGuestUserNotShown() {
+        UsersPreferenceProvider provider = createProvider();
+
+        List<Preference> userList = provider.createUserList();
+        assertThat(userList.size()).isEqualTo(4); // 3 real users + guest item.
+        assertThat(userList.get(0).getTitle()).isEqualTo(
+                mContext.getString(R.string.current_user_name, TEST_CURRENT_USER_NAME));
+        assertThat(userList.get(1).getTitle()).isEqualTo(TEST_OTHER_USER_1_NAME);
+        assertThat(userList.get(2).getTitle()).isEqualTo(TEST_OTHER_USER_2_NAME);
+    }
+
+    @Test
+    public void testCreateUserList_guestShownAsSeparateLastElement() {
+        UsersPreferenceProvider provider = createProvider();
+
+        List<Preference> userList = provider.createUserList();
+        assertThat(userList.get(userList.size() - 1).getTitle()).isEqualTo(
+                mContext.getString(R.string.user_guest));
+    }
+
+    @Test
+    public void testCreateUserList_currentUserNotShown() {
+        UsersPreferenceProvider provider = createProvider();
+        provider.setIncludeCurrentUser(false);
+
+        List<Preference> userList = provider.createUserList();
+        assertThat(userList.size()).isEqualTo(3); // 3 real users + guest item.
+        assertThat(userList.get(0).getTitle()).isEqualTo(TEST_OTHER_USER_1_NAME);
+        assertThat(userList.get(1).getTitle()).isEqualTo(TEST_OTHER_USER_2_NAME);
+        assertThat(userList.get(2).getTitle()).isEqualTo(
+                mContext.getString(R.string.user_guest));
+    }
+
+    @Test
+    public void testCreateUserList_guestNotShown() {
+        UsersPreferenceProvider provider = createProvider();
+        provider.setIncludeGuest(false);
+
+        List<Preference> userList = provider.createUserList();
+        assertThat(userList.size()).isEqualTo(3); // 3 real users.
+        assertThat(userList.get(0).getTitle()).isEqualTo(
+                mContext.getString(R.string.current_user_name, TEST_CURRENT_USER_NAME));
+        assertThat(userList.get(1).getTitle()).isEqualTo(TEST_OTHER_USER_1_NAME);
+        assertThat(userList.get(2).getTitle()).isEqualTo(TEST_OTHER_USER_2_NAME);
+    }
+
+    @Test
+    public void testPerformClick_currentUser_invokesUserClickListener() {
+        UsersPreferenceProvider provider = createProvider();
+
+        List<Preference> userList = provider.createUserList();
+        userList.get(0).performClick();
+        verify(mUserClickListener).onUserClicked(TEST_CURRENT_USER);
+    }
+
+    @Test
+    public void testPerformClick_otherUser_invokesUserClickListener() {
+        UsersPreferenceProvider provider = createProvider();
+
+        List<Preference> userList = provider.createUserList();
+        userList.get(1).performClick();
+        verify(mUserClickListener).onUserClicked(TEST_OTHER_USER_1);
+    }
+
+    @Test
+    public void testPerformClick_guestUser_doesntInvokeUserClickListener() {
+        UsersPreferenceProvider provider = createProvider();
+
+        List<Preference> userList = provider.createUserList();
+        userList.get(userList.size() - 1).performClick();
+        verify(mUserClickListener, never()).onUserClicked(any(UserInfo.class));
+    }
+
+    private UsersPreferenceProvider createProvider() {
+        return new UsersPreferenceProvider(mContext, mCarUserManagerHelper, mUserClickListener);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/AccessPointListPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/AccessPointListPreferenceControllerTest.java
new file mode 100644
index 0000000..f8cc53a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/AccessPointListPreferenceControllerTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.util.Pair;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarWifiManager;
+import com.android.car.settings.testutils.ShadowWifiManager;
+import com.android.car.settings.wifi.details.WifiDetailsFragment;
+import com.android.settingslib.wifi.AccessPoint;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarWifiManager.class, ShadowWifiManager.class})
+public class AccessPointListPreferenceControllerTest {
+    private static final int SIGNAL_LEVEL = 1;
+    @Mock
+    private AccessPoint mMockAccessPoint1;
+    @Mock
+    private AccessPoint mMockAccessPoint2;
+    @Mock
+    private CarWifiManager mMockCarWifiManager;
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private AccessPointListPreferenceController mController;
+    private FragmentController mFragmentController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarWifiManager.setInstance(mMockCarWifiManager);
+        mContext = RuntimeEnvironment.application;
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(PackageManager.FEATURE_WIFI,
+                true);
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        PreferenceControllerTestHelper<AccessPointListPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        AccessPointListPreferenceController.class, mPreferenceGroup);
+        mController = controllerHelper.getController();
+        mFragmentController = controllerHelper.getMockFragmentController();
+
+        when(mMockAccessPoint1.getSecurity()).thenReturn(AccessPoint.SECURITY_NONE);
+        when(mMockAccessPoint1.getLevel()).thenReturn(SIGNAL_LEVEL);
+        when(mMockAccessPoint2.getSecurity()).thenReturn(AccessPoint.SECURITY_NONE);
+        when(mMockAccessPoint2.getLevel()).thenReturn(SIGNAL_LEVEL);
+
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarWifiManager.reset();
+    }
+
+    @Test
+    public void refreshUi_emptyList_notVisible() {
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(new ArrayList<>());
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isEqualTo(false);
+    }
+
+    @Test
+    public void refreshUi_notEmpty_visible() {
+        List<AccessPoint> accessPointList = Arrays.asList(mMockAccessPoint1);
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(accessPointList);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.isVisible()).isEqualTo(true);
+    }
+
+    @Test
+    public void refreshUi_notEmpty_listCount() {
+        List<AccessPoint> accessPointList = Arrays.asList(mMockAccessPoint1);
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(accessPointList);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(accessPointList.size());
+    }
+
+    @Test
+    public void onUxRestrictionsChanged_switchToSavedApOnly() {
+        List<AccessPoint> allAccessPointList = Arrays.asList(mMockAccessPoint1, mMockAccessPoint2);
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(allAccessPointList);
+        List<AccessPoint> savedAccessPointList = Arrays.asList(mMockAccessPoint1);
+        when(mMockCarWifiManager.getSavedAccessPoints()).thenReturn(savedAccessPointList);
+        mController.refreshUi();
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(allAccessPointList.size());
+
+        CarUxRestrictions noSetupRestrictions = new CarUxRestrictions.Builder(
+                true, CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP, 0).build();
+        mController.onUxRestrictionsChanged(noSetupRestrictions);
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(savedAccessPointList.size());
+    }
+
+    @Test
+    public void performClick_noSecurityNotConnectedAccessPoint_connect() {
+        when(mMockAccessPoint1.getSecurity()).thenReturn(AccessPoint.SECURITY_NONE);
+        when(mMockAccessPoint1.isSaved()).thenReturn(false);
+        when(mMockAccessPoint1.isActive()).thenReturn(false);
+        List<AccessPoint> accessPointList = Arrays.asList(mMockAccessPoint1);
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(accessPointList);
+        mController.refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+        verify(mMockCarWifiManager).connectToPublicWifi(eq(mMockAccessPoint1), any());
+    }
+
+    @Test
+    public void performClick_activeAccessPoint_showDetailsFragment() {
+        when(mMockAccessPoint1.isActive()).thenReturn(true);
+        List<AccessPoint> accessPointList = Arrays.asList(mMockAccessPoint1);
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(accessPointList);
+        mController.refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+        verify(mFragmentController).launchFragment(any(WifiDetailsFragment.class));
+    }
+
+    @Test
+    public void performClick_savedAccessPoint_connect() {
+        when(mMockAccessPoint1.isSaved()).thenReturn(true);
+        when(mMockAccessPoint1.isActive()).thenReturn(false);
+        List<AccessPoint> accessPointList = Arrays.asList(mMockAccessPoint1);
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(accessPointList);
+        mController.refreshUi();
+
+        mPreferenceGroup.getPreference(0).performClick();
+        verify(mMockCarWifiManager).connectToSavedWifi(eq(mMockAccessPoint1), any());
+    }
+
+    @Test
+    public void callChangeListener_newSecureAccessPoint_wifiAdded() {
+        String ssid = "test_ssid";
+        String password = "test_password";
+        when(mMockAccessPoint1.getSsid()).thenReturn(ssid);
+        when(mMockAccessPoint1.getSecurity()).thenReturn(AccessPoint.SECURITY_PSK);
+        when(mMockAccessPoint1.isSaved()).thenReturn(false);
+        when(mMockAccessPoint1.isActive()).thenReturn(false);
+        List<AccessPoint> accessPointList = Arrays.asList(mMockAccessPoint1);
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(accessPointList);
+        mController.refreshUi();
+
+        mPreferenceGroup.getPreference(0).callChangeListener(password);
+        WifiConfiguration lastAdded = getShadowWifiManager().getLastAddedNetworkConfiguration();
+
+        assertThat(lastAdded.SSID).contains(ssid);
+        assertThat(lastAdded.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA_PSK);
+        assertThat(lastAdded.preSharedKey).contains(password);
+    }
+
+    @Test
+    public void callChangeListener_newSecureAccessPoint_wifiEnabled() {
+        String ssid = "test_ssid";
+        String password = "test_password";
+        when(mMockAccessPoint1.getSsid()).thenReturn(ssid);
+        when(mMockAccessPoint1.getSecurity()).thenReturn(AccessPoint.SECURITY_PSK);
+        when(mMockAccessPoint1.isSaved()).thenReturn(false);
+        when(mMockAccessPoint1.isActive()).thenReturn(false);
+        List<AccessPoint> accessPointList = Arrays.asList(mMockAccessPoint1);
+        when(mMockCarWifiManager.getAllAccessPoints()).thenReturn(accessPointList);
+        mController.refreshUi();
+
+        mPreferenceGroup.getPreference(0).callChangeListener(password);
+        Pair<Integer, Boolean> lastEnabled = getShadowWifiManager().getLastEnabledNetwork();
+
+        // Enable should be called on the most recently added network id.
+        assertThat(lastEnabled.first).isEqualTo(getShadowWifiManager().getLastAddedNetworkId());
+        // WifiUtil will try to enable the network right away.
+        assertThat(lastEnabled.second).isTrue();
+    }
+
+    private ShadowWifiManager getShadowWifiManager() {
+        return Shadow.extract(mContext.getSystemService(WifiManager.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/AccessPointPreferenceTest.java b/tests/robotests/src/com/android/car/settings/wifi/AccessPointPreferenceTest.java
new file mode 100644
index 0000000..2e6a5e1
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/AccessPointPreferenceTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowAlertDialog;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AccessPointPreferenceTest {
+
+    private static final String TEST_KEY = "test_key";
+    private AccessPointPreference mPreference;
+
+    @Mock
+    private AccessPoint mAccessPoint;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = RuntimeEnvironment.application;
+        FragmentController<TestSettingsFragment> fragmentController = FragmentController.of(
+                new TestSettingsFragment());
+        TestSettingsFragment fragment = fragmentController.get();
+        fragmentController.setup();
+
+        mPreference = new AccessPointPreference(context, mAccessPoint);
+        mPreference.setKey(TEST_KEY);
+        fragment.getPreferenceScreen().addPreference(mPreference);
+    }
+
+    @Test
+    public void onClick_securityTypeNone_doesntOpenDialog() {
+        when(mAccessPoint.getSecurity()).thenReturn(AccessPoint.SECURITY_NONE);
+        mPreference.onClick();
+
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        assertThat(dialog).isNull();
+    }
+
+    @Test
+    public void onClick_hasSecurity_isSaved_correctPassword_doesntOpenDialog() {
+        WifiConfiguration config = mock(WifiConfiguration.class);
+        WifiConfiguration.NetworkSelectionStatus status = mock(
+                WifiConfiguration.NetworkSelectionStatus.class);
+        when(mAccessPoint.getSecurity()).thenReturn(AccessPoint.SECURITY_PSK);
+        when(mAccessPoint.isSaved()).thenReturn(true);
+        when(mAccessPoint.getConfig()).thenReturn(config);
+        when(config.getNetworkSelectionStatus()).thenReturn(status);
+        when(status.isNetworkEnabled()).thenReturn(true);
+        mPreference.onClick();
+
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        assertThat(dialog).isNull();
+
+    }
+
+    @Test
+    public void onClick_hasSecurity_isSaved_incorrectPassword_opensDialog() {
+        WifiConfiguration config = mock(WifiConfiguration.class);
+        WifiConfiguration.NetworkSelectionStatus status = mock(
+                WifiConfiguration.NetworkSelectionStatus.class);
+        when(mAccessPoint.getSecurity()).thenReturn(AccessPoint.SECURITY_PSK);
+        when(mAccessPoint.isSaved()).thenReturn(true);
+        when(mAccessPoint.getConfig()).thenReturn(config);
+        when(config.getNetworkSelectionStatus()).thenReturn(status);
+        when(status.isNetworkEnabled()).thenReturn(false);
+        when(status.getNetworkSelectionDisableReason()).thenReturn(
+                WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD);
+        mPreference.onClick();
+
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        assertThat(dialog).isNotNull();
+    }
+
+    @Test
+    public void onClick_hasSecurity_isNotSaved_opensDialog() {
+        when(mAccessPoint.getSecurity()).thenReturn(AccessPoint.SECURITY_PSK);
+        when(mAccessPoint.isSaved()).thenReturn(false);
+        mPreference.onClick();
+
+        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+        assertThat(dialog).isNotNull();
+    }
+
+    /** Concrete {@link SettingsFragment} for testing. */
+    public static class TestSettingsFragment extends SettingsFragment {
+        @Override
+        protected int getPreferenceScreenResId() {
+            return R.xml.settings_fragment;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/AddWifiFragmentTest.java b/tests/robotests/src/com/android/car/settings/wifi/AddWifiFragmentTest.java
new file mode 100644
index 0000000..b48d36f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/AddWifiFragmentTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.widget.Button;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowLocalBroadcastManager;
+import com.android.car.settings.testutils.ShadowWifiManager;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowLocalBroadcastManager.class, ShadowWifiManager.class})
+public class AddWifiFragmentTest {
+
+    private Context mContext;
+    private LocalBroadcastManager mLocalBroadcastManager;
+    private AddWifiFragment mFragment;
+    private FragmentController<AddWifiFragment> mFragmentController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mLocalBroadcastManager = LocalBroadcastManager.getInstance(mContext);
+        mFragment = new AddWifiFragment();
+        mFragmentController = FragmentController.of(mFragment);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowLocalBroadcastManager.reset();
+        ShadowWifiManager.reset();
+    }
+
+    @Test
+    public void onStart_registersNameChangeListener() {
+        mFragmentController.create().start();
+
+        assertThat(isReceiverRegisteredForAction(
+                NetworkNamePreferenceController.ACTION_NAME_CHANGE)).isTrue();
+    }
+
+    @Test
+    public void onStart_registersSecurityChangeListener() {
+        mFragmentController.create().start();
+
+        assertThat(isReceiverRegisteredForAction(
+                NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE)).isTrue();
+    }
+
+    @Test
+    public void onStop_unregistersNameChangeListener() {
+        mFragmentController.create().start();
+        mFragmentController.stop();
+
+        assertThat(isReceiverRegisteredForAction(
+                NetworkNamePreferenceController.ACTION_NAME_CHANGE)).isFalse();
+    }
+
+    @Test
+    public void onStop_unregistersSecurityChangeListener() {
+        mFragmentController.create().start();
+        mFragmentController.stop();
+
+        assertThat(isReceiverRegisteredForAction(
+                NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE)).isFalse();
+    }
+
+    @Test
+    public void initialState_buttonDisabled() {
+        mFragmentController.setup();
+        assertThat(getAddWifiButton().isEnabled()).isFalse();
+    }
+
+    @Test
+    public void receiveNameChangeIntent_emptyName_buttonDisabled() {
+        mFragmentController.setup();
+        Intent intent = new Intent(NetworkNamePreferenceController.ACTION_NAME_CHANGE);
+        intent.putExtra(NetworkNamePreferenceController.KEY_NETWORK_NAME, "");
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        assertThat(getAddWifiButton().isEnabled()).isFalse();
+    }
+
+    @Test
+    public void receiveNameChangeIntent_name_buttonEnabled() {
+        mFragmentController.setup();
+        String networkName = "test_network_name";
+        Intent intent = new Intent(NetworkNamePreferenceController.ACTION_NAME_CHANGE);
+        intent.putExtra(NetworkNamePreferenceController.KEY_NETWORK_NAME, networkName);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        assertThat(getAddWifiButton().isEnabled()).isTrue();
+    }
+
+    @Test
+    public void receiveSecurityChangeIntent_nameSet_buttonDisabled() {
+        mFragmentController.setup();
+        String networkName = "test_network_name";
+        Intent intent = new Intent(NetworkNamePreferenceController.ACTION_NAME_CHANGE);
+        intent.putExtra(NetworkNamePreferenceController.KEY_NETWORK_NAME, networkName);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        intent = new Intent(NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE);
+        intent.putExtra(NetworkSecurityPreferenceController.KEY_SECURITY_TYPE,
+                AccessPoint.SECURITY_PSK);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        assertThat(getAddWifiButton().isEnabled()).isFalse();
+    }
+
+    private Button getAddWifiButton() {
+        return mFragment.requireActivity().findViewById(R.id.action_button1);
+    }
+
+    private boolean isReceiverRegisteredForAction(String action) {
+        List<ShadowLocalBroadcastManager.Wrapper> receivers =
+                ShadowLocalBroadcastManager.getRegisteredBroadcastReceivers();
+
+        boolean found = false;
+        for (ShadowLocalBroadcastManager.Wrapper receiver : receivers) {
+            if (receiver.getIntentFilter().hasAction(action)) {
+                found = true;
+            }
+        }
+
+        return found;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/AddWifiPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/AddWifiPreferenceControllerTest.java
new file mode 100644
index 0000000..f02fb8b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/AddWifiPreferenceControllerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AddWifiPreferenceControllerTest {
+
+    private static final List<Integer> VISIBLE_STATES = Arrays.asList(
+            WifiManager.WIFI_STATE_ENABLED,
+            WifiManager.WIFI_STATE_DISABLING,
+            WifiManager.WIFI_STATE_ENABLING,
+            WifiManager.WIFI_STATE_UNKNOWN);
+    private static final List<Integer> INVISIBLE_STATES = Arrays.asList(
+            WifiManager.WIFI_STATE_DISABLED);
+
+    private Preference mPreference;
+    private AddWifiPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        Context context = RuntimeEnvironment.application;
+        mPreference = new Preference(context);
+        PreferenceControllerTestHelper<AddWifiPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(context, AddWifiPreferenceController.class,
+                        mPreference);
+        mController = controllerHelper.getController();
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void onWifiStateChanged_invisible() {
+        for (int state : INVISIBLE_STATES) {
+            mController.onWifiStateChanged(state);
+            assertThat(mPreference.isVisible()).isEqualTo(false);
+        }
+    }
+
+    @Test
+    public void onWifiStateChanged_visible() {
+        for (int state : VISIBLE_STATES) {
+            mController.onWifiStateChanged(state);
+            assertThat(mPreference.isVisible()).isEqualTo(true);
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/NetworkNamePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/NetworkNamePreferenceControllerTest.java
new file mode 100644
index 0000000..6f8364a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/NetworkNamePreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.EditTextPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowLocalBroadcastManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowLocalBroadcastManager.class})
+public class NetworkNamePreferenceControllerTest {
+
+    private static final String TEST_SSID = "test_ssid";
+
+    private Context mContext;
+    private EditTextPreference mEditTextPreference;
+    private NetworkNamePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mEditTextPreference = new EditTextPreference(mContext);
+        PreferenceControllerTestHelper<NetworkNamePreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        NetworkNamePreferenceController.class, mEditTextPreference);
+        mController = controllerHelper.getController();
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowLocalBroadcastManager.reset();
+    }
+
+    @Test
+    public void refreshUi_defaultState_showsDefaultString() {
+        mController.refreshUi();
+        assertThat(mEditTextPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.default_network_name_summary));
+    }
+
+    @Test
+    public void handlePreferenceChanged_newTextIsSet() {
+        mEditTextPreference.setText("Old value");
+        mEditTextPreference.callChangeListener("New value");
+        assertThat(mEditTextPreference.getSummary()).isEqualTo("New value");
+    }
+
+    @Test
+    public void handlePreferenceChanged_broadcastIsSent() {
+        String value = "New value";
+        mEditTextPreference.callChangeListener(value);
+
+        List<Intent> intents = ShadowLocalBroadcastManager.getSentBroadcastIntents();
+        assertThat(intents).hasSize(1);
+        assertThat(intents.get(0).getAction()).isEqualTo(
+                NetworkNamePreferenceController.ACTION_NAME_CHANGE);
+        assertThat(intents.get(0).getStringExtra(
+                NetworkNamePreferenceController.KEY_NETWORK_NAME)).isEqualTo(value);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/NetworkNameRestrictedPasswordEditTextPreferenceTest.java b/tests/robotests/src/com/android/car/settings/wifi/NetworkNameRestrictedPasswordEditTextPreferenceTest.java
new file mode 100644
index 0000000..cbcef40
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/NetworkNameRestrictedPasswordEditTextPreferenceTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.car.settings.testutils.FragmentController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowDialog;
+import org.robolectric.shadows.ShadowToast;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class NetworkNameRestrictedPasswordEditTextPreferenceTest {
+
+    private static final String KEY = "test_key";
+
+    private Context mContext;
+    private NetworkNameRestrictedPasswordEditTextPreference mPreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        FragmentController<TestSettingsFragment> fragmentController = FragmentController.of(
+                new TestSettingsFragment());
+        TestSettingsFragment fragment = fragmentController.get();
+        fragmentController.setup();
+
+        mPreference = new NetworkNameRestrictedPasswordEditTextPreference(mContext);
+        mPreference.setKey(KEY);
+        fragment.getPreferenceScreen().addPreference(mPreference);
+    }
+
+    @Test
+    public void performClick_noName_toastShown() {
+        mPreference.performClick();
+
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_no_network_name));
+    }
+
+    @Test
+    public void performClick_hasName_showsDialog() {
+        mPreference.setNetworkName("test_name");
+        mPreference.performClick();
+
+        assertThat(ShadowDialog.getLatestDialog()).isNotNull();
+    }
+
+    /** Concrete {@link SettingsFragment} for testing. */
+    public static class TestSettingsFragment extends SettingsFragment {
+        @Override
+        protected int getPreferenceScreenResId() {
+            return R.xml.settings_fragment;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/NetworkPasswordPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/NetworkPasswordPreferenceControllerTest.java
new file mode 100644
index 0000000..28b555a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/NetworkPasswordPreferenceControllerTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.util.Pair;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowLocalBroadcastManager;
+import com.android.car.settings.testutils.ShadowWifiManager;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowLocalBroadcastManager.class, ShadowWifiManager.class})
+public class NetworkPasswordPreferenceControllerTest {
+
+    private Context mContext;
+    private LocalBroadcastManager mLocalBroadcastManager;
+    private NetworkNameRestrictedPasswordEditTextPreference mPasswordEditTextPreference;
+    private PreferenceControllerTestHelper<NetworkPasswordPreferenceController>
+            mPreferenceControllerHelper;
+    private NetworkPasswordPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mLocalBroadcastManager = LocalBroadcastManager.getInstance(mContext);
+        mPasswordEditTextPreference = new NetworkNameRestrictedPasswordEditTextPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                NetworkPasswordPreferenceController.class, mPasswordEditTextPreference);
+        mController = mPreferenceControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowLocalBroadcastManager.reset();
+        ShadowWifiManager.reset();
+    }
+
+    @Test
+    public void onStart_registersNameChangeListener() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(isReceiverRegisteredForAction(
+                NetworkNamePreferenceController.ACTION_NAME_CHANGE)).isTrue();
+    }
+
+    @Test
+    public void onStart_registersSecurityChangeListener() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(isReceiverRegisteredForAction(
+                NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE)).isTrue();
+    }
+
+    @Test
+    public void onStop_unregistersNameChangeListener() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(isReceiverRegisteredForAction(
+                NetworkNamePreferenceController.ACTION_NAME_CHANGE)).isFalse();
+    }
+
+    @Test
+    public void onStop_unregistersSecurityChangeListener() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(isReceiverRegisteredForAction(
+                NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE)).isFalse();
+    }
+
+    @Test
+    public void receiveNameChangeIntent_emptyName_dialogNameRemoved() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        Intent intent = new Intent(NetworkNamePreferenceController.ACTION_NAME_CHANGE);
+        intent.putExtra(NetworkNamePreferenceController.KEY_NETWORK_NAME, "");
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        assertThat(mPasswordEditTextPreference.getDialogTitle()).isEqualTo(
+                mContext.getString(R.string.wifi_password));
+    }
+
+    @Test
+    public void receiveNameChangeIntent_name_dialogNameSet() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        String networkName = "test_network_name";
+        Intent intent = new Intent(NetworkNamePreferenceController.ACTION_NAME_CHANGE);
+        intent.putExtra(NetworkNamePreferenceController.KEY_NETWORK_NAME, networkName);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        assertThat(mPasswordEditTextPreference.getDialogTitle()).isEqualTo(networkName);
+    }
+
+    @Test
+    public void receiveSecurityChangeIntent_setUnsecureType_preferenceHidden() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        Intent intent = new Intent(NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE);
+        intent.putExtra(NetworkSecurityPreferenceController.KEY_SECURITY_TYPE,
+                AccessPoint.SECURITY_NONE);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        assertThat(mPasswordEditTextPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void receiveSecurityChangeIntent_setSecureType_preferenceVisible() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        Intent intent = new Intent(NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE);
+        intent.putExtra(NetworkSecurityPreferenceController.KEY_SECURITY_TYPE,
+                AccessPoint.SECURITY_PSK);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        assertThat(mPasswordEditTextPreference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void handlePreferenceChanged_hasSecurity_networkNameSet_wifiAdded() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        String networkName = "network_name";
+        String password = "password";
+        Intent intent = new Intent(NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE);
+        intent.putExtra(NetworkSecurityPreferenceController.KEY_SECURITY_TYPE,
+                AccessPoint.SECURITY_PSK);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        intent = new Intent(NetworkNamePreferenceController.ACTION_NAME_CHANGE);
+        intent.putExtra(NetworkNamePreferenceController.KEY_NETWORK_NAME, networkName);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+        mPasswordEditTextPreference.callChangeListener(password);
+
+        WifiConfiguration lastAdded = getShadowWifiManager().getLastAddedNetworkConfiguration();
+        assertThat(lastAdded.SSID).contains(networkName);
+        assertThat(lastAdded.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA_PSK);
+        assertThat(lastAdded.preSharedKey).contains(password);
+    }
+
+    @Test
+    public void handlePreferenceChanged_hasSecurity_networkNameSet_wifiEnabled() {
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        String networkName = "network_name";
+        String password = "password";
+        Intent intent = new Intent(NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE);
+        intent.putExtra(NetworkSecurityPreferenceController.KEY_SECURITY_TYPE,
+                AccessPoint.SECURITY_PSK);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+
+        intent = new Intent(NetworkNamePreferenceController.ACTION_NAME_CHANGE);
+        intent.putExtra(NetworkNamePreferenceController.KEY_NETWORK_NAME, networkName);
+        mLocalBroadcastManager.sendBroadcastSync(intent);
+        mPasswordEditTextPreference.callChangeListener(password);
+
+        Pair<Integer, Boolean> lastEnabled = getShadowWifiManager().getLastEnabledNetwork();
+        // Enable should be called on the most recently added network id.
+        assertThat(lastEnabled.first).isEqualTo(getShadowWifiManager().getLastAddedNetworkId());
+        // WifiUtil will try to enable the network right away.
+        assertThat(lastEnabled.second).isTrue();
+    }
+
+    private ShadowWifiManager getShadowWifiManager() {
+        return Shadow.extract(mContext.getSystemService(WifiManager.class));
+    }
+
+    private boolean isReceiverRegisteredForAction(String action) {
+        List<ShadowLocalBroadcastManager.Wrapper> receivers =
+                ShadowLocalBroadcastManager.getRegisteredBroadcastReceivers();
+
+        boolean found = false;
+        for (ShadowLocalBroadcastManager.Wrapper receiver : receivers) {
+            if (receiver.getIntentFilter().hasAction(action)) {
+                found = true;
+            }
+        }
+
+        return found;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/NetworkSecurityPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/NetworkSecurityPreferenceControllerTest.java
new file mode 100644
index 0000000..361e107
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/NetworkSecurityPreferenceControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowLocalBroadcastManager;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowLocalBroadcastManager.class})
+public class NetworkSecurityPreferenceControllerTest {
+
+    private Context mContext;
+    private ListPreference mListPreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mListPreference = new ListPreference(mContext);
+        PreferenceControllerTestHelper<NetworkSecurityPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        NetworkSecurityPreferenceController.class, mListPreference);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowLocalBroadcastManager.reset();
+    }
+
+    @Test
+    public void handlePreferenceChanged_unsecureNetwork_summaryUpdated() {
+        String value = Integer.toString(AccessPoint.SECURITY_NONE);
+        mListPreference.callChangeListener(value);
+
+        assertThat(mListPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.wifi_security_none));
+    }
+
+    @Test
+    public void handlePreferenceChanged_pskNetwork_summaryUpdated() {
+        String value = Integer.toString(AccessPoint.SECURITY_PSK);
+        mListPreference.callChangeListener(value);
+
+        assertThat(mListPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.wifi_security_psk_generic));
+    }
+
+    @Test
+    public void handlePreferenceChanged_broadcastIsSent() {
+        String value = Integer.toString(AccessPoint.SECURITY_PSK);
+        mListPreference.callChangeListener(value);
+
+        List<Intent> intents = ShadowLocalBroadcastManager.getSentBroadcastIntents();
+        assertThat(intents).hasSize(1);
+        assertThat(intents.get(0).getAction()).isEqualTo(
+                NetworkSecurityPreferenceController.ACTION_SECURITY_CHANGE);
+        assertThat(intents.get(0).getIntExtra(NetworkSecurityPreferenceController.KEY_SECURITY_TYPE,
+                AccessPoint.SECURITY_NONE)).isEqualTo(Integer.parseInt(value));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiEntryPreferenceControllerTest.java
new file mode 100644
index 0000000..e8fc536
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiEntryPreferenceControllerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import static com.android.car.settings.common.PreferenceController.AVAILABLE;
+import static com.android.car.settings.common.PreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.MasterSwitchPreference;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarWifiManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link WifiEntryPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarWifiManager.class})
+public class WifiEntryPreferenceControllerTest {
+
+    private Context mContext;
+    private MasterSwitchPreference mMasterSwitchPreference;
+    private PreferenceControllerTestHelper<WifiEntryPreferenceController> mControllerHelper;
+    private WifiEntryPreferenceController mController;
+    @Mock
+    private CarWifiManager mCarWifiManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+        mContext = RuntimeEnvironment.application;
+        mMasterSwitchPreference = new MasterSwitchPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                WifiEntryPreferenceController.class, mMasterSwitchPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarWifiManager.reset();
+    }
+
+    @Test
+    public void onCreate_setsListener() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ true);
+        assertThat(mMasterSwitchPreference.getSwitchToggleListener()).isNull();
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        assertThat(mMasterSwitchPreference.getSwitchToggleListener()).isNotNull();
+    }
+
+    @Test
+    public void refreshUi_wifiDisabled_setsSwitchUnchecked() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ true);
+        when(mCarWifiManager.isWifiEnabled()).thenReturn(false);
+        mMasterSwitchPreference.setSwitchChecked(true);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+        assertThat(mMasterSwitchPreference.isSwitchChecked()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_wifiEnabled_setsSwitchChecked() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ true);
+        when(mCarWifiManager.isWifiEnabled()).thenReturn(true);
+        mMasterSwitchPreference.setSwitchChecked(false);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        mController.refreshUi();
+        assertThat(mMasterSwitchPreference.isSwitchChecked()).isTrue();
+    }
+
+    @Test
+    public void getAvailabilityStatus_wifiAvailable_available() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_wifiNotAvailable_unsupportedOnDevice() {
+        Shadows.shadowOf(mContext.getPackageManager()).setSystemFeature(
+                PackageManager.FEATURE_WIFI, /* supported= */ false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiStatusPreferenceControllerTest.java
new file mode 100644
index 0000000..32692dd
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiStatusPreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiStatusPreferenceControllerTest {
+    private static final List<Integer> VISIBLE_STATES = Arrays.asList(
+            WifiManager.WIFI_STATE_DISABLED,
+            WifiManager.WIFI_STATE_ENABLING);
+    private static final List<Integer> INVISIBLE_STATES = Arrays.asList(
+            WifiManager.WIFI_STATE_ENABLED,
+            WifiManager.WIFI_STATE_DISABLING,
+            WifiManager.WIFI_STATE_UNKNOWN);
+
+    private Context mContext;
+    private Preference mPreference;
+    private WifiStatusPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        PreferenceControllerTestHelper<WifiStatusPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        WifiStatusPreferenceController.class, mPreference);
+        mController = controllerHelper.getController();
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void onWifiStateChanged_invisible() {
+        for (int state : INVISIBLE_STATES) {
+            mController.onWifiStateChanged(state);
+            assertThat(mPreference.isVisible()).isEqualTo(false);
+        }
+    }
+
+    @Test
+    public void onWifiStateChanged_visible() {
+        for (int state : VISIBLE_STATES) {
+            mController.onWifiStateChanged(state);
+            assertThat(mPreference.isVisible()).isEqualTo(true);
+        }
+    }
+
+    @Test
+    public void onWifiStateChanged_disabled() {
+        mController.onWifiStateChanged(WifiManager.WIFI_STATE_DISABLED);
+        assertThat(mPreference.getTitle())
+                .isEqualTo(mContext.getResources().getString(R.string.wifi_disabled));
+    }
+
+    @Test
+    public void onWifiStateChanged_enabling() {
+        mController.onWifiStateChanged(WifiManager.WIFI_STATE_ENABLING);
+        assertThat(mPreference.getTitle())
+                .isEqualTo(mContext.getResources().getString(R.string.loading_wifi_list));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiTetherApBandPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherApBandPreferenceControllerTest.java
new file mode 100644
index 0000000..43fa83e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherApBandPreferenceControllerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarWifiManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarWifiManager.class})
+public class WifiTetherApBandPreferenceControllerTest {
+
+    private Context mContext;
+    private ListPreference mPreference;
+    private PreferenceControllerTestHelper<WifiTetherApBandPreferenceController>
+            mControllerHelper;
+    private CarWifiManager mCarWifiManager;
+    private WifiTetherApBandPreferenceController mController;
+
+    @Before
+    public void setup() {
+        mContext = RuntimeEnvironment.application;
+        mCarWifiManager = new CarWifiManager(mContext);
+        mPreference = new ListPreference(mContext);
+        mControllerHelper =
+                new PreferenceControllerTestHelper<WifiTetherApBandPreferenceController>(mContext,
+                        WifiTetherApBandPreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarWifiManager.reset();
+    }
+
+    @Test
+    public void onStart_5GhzBandNotSupported_preferenceIsNotEnabled() {
+        ShadowCarWifiManager.setIsDualBandSupported(false);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(!mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onStart_5GhzBandNotSupported_summarySetToChoose2Ghz() {
+        ShadowCarWifiManager.setIsDualBandSupported(false);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(mContext.getString(R.string.wifi_ap_choose_2G));
+    }
+
+    @Test
+    public void onStart_5GhzBandIsSupported_preferenceIsEnabled() {
+        ShadowCarWifiManager.setIsDualBandSupported(true);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onStart_wifiConfigApBandSetTo2Ghz_valueIsSetTo2Ghz() {
+        ShadowCarWifiManager.setIsDualBandSupported(true);
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_2GHZ;
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreference.getValue())
+                .isEqualTo(Integer.toString(WifiConfiguration.AP_BAND_2GHZ));
+    }
+
+    @Test
+    public void onStart_wifiConfigApBandSetTo5Ghz_valueIsSetTo5Ghz() {
+        ShadowCarWifiManager.setIsDualBandSupported(true);
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_5GHZ;
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreference.getValue())
+                .isEqualTo(Integer.toString(WifiConfiguration.AP_BAND_5GHZ));
+    }
+
+    @Test
+    public void onPreferenceChangedTo5Ghz_updatesApBandConfigTo5Ghz() {
+        ShadowCarWifiManager.setIsDualBandSupported(true);
+        ShadowCarWifiManager.setIsDualModeSupported(false);
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_2GHZ;
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.handlePreferenceChanged(mPreference,
+                Integer.toString(WifiConfiguration.AP_BAND_5GHZ));
+
+        assertThat(mCarWifiManager.getWifiApConfig().apBand)
+                .isEqualTo(WifiConfiguration.AP_BAND_5GHZ);
+    }
+
+    @Test
+    public void onPreferenceChangedTo2Ghz_updatesApBandConfigTo2Ghz() {
+        ShadowCarWifiManager.setIsDualBandSupported(true);
+        ShadowCarWifiManager.setIsDualModeSupported(false);
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_5GHZ;
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.handlePreferenceChanged(mPreference,
+                Integer.toString(WifiConfiguration.AP_BAND_2GHZ));
+
+        assertThat(mCarWifiManager.getWifiApConfig().apBand)
+                .isEqualTo(WifiConfiguration.AP_BAND_2GHZ);
+    }
+
+    @Test
+    public void onStart_dualModeIsSupported_summarySetToPrefer5Ghz() {
+        ShadowCarWifiManager.setIsDualBandSupported(true);
+        ShadowCarWifiManager.setIsDualModeSupported(true);
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_5GHZ;
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(mPreference.getSummary()).isEqualTo(
+                mContext.getString(R.string.wifi_ap_prefer_5G));
+    }
+
+    @Test
+    public void onPreferenceChangedTo5Ghz_dualModeIsSupported_defaultToApBandAny() {
+        ShadowCarWifiManager.setIsDualBandSupported(true);
+        ShadowCarWifiManager.setIsDualModeSupported(true);
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_2GHZ;
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.handlePreferenceChanged(mPreference,
+                Integer.toString(WifiConfiguration.AP_BAND_5GHZ));
+
+        assertThat(mCarWifiManager.getWifiApConfig().apBand)
+                .isEqualTo(WifiConfiguration.AP_BAND_ANY);
+    }
+
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiTetherAutoOffPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherAutoOffPreferenceControllerTest.java
new file mode 100644
index 0000000..b179b72
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherAutoOffPreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiTetherAutoOffPreferenceControllerTest {
+
+    private Context mContext;
+    private TwoStatePreference mTwoStatePreference;
+    private PreferenceControllerTestHelper<WifiTetherAutoOffPreferenceController> mControllerHelper;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mTwoStatePreference = new SwitchPreference(mContext);
+        mControllerHelper =
+                new PreferenceControllerTestHelper<WifiTetherAutoOffPreferenceController>(mContext,
+                        WifiTetherAutoOffPreferenceController.class, mTwoStatePreference);
+    }
+
+    @Test
+    public void onStart_tetherAutoOff_on_shouldReturnSwitchStateOn() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mTwoStatePreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onStart_tetherAutoOff_off_shouldReturnSwitchStateOff() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 0);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mTwoStatePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onSwitchOn_shouldReturnAutoOff_on() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 0);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mTwoStatePreference.performClick();
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 0))
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void onSwitchOff_shouldReturnAutoOff_off() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mTwoStatePreference.performClick();
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1))
+                .isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiTetherBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherBasePreferenceControllerTest.java
new file mode 100644
index 0000000..0335364
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherBasePreferenceControllerTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ValidatedEditTextPreference;
+import com.android.car.settings.testutils.ShadowCarWifiManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarWifiManager.class})
+public class WifiTetherBasePreferenceControllerTest {
+
+    private static final String SUMMARY = "SUMMARY";
+    private static final String DEFAULT_SUMMARY = "DEFAULT_SUMMARY";
+
+    private static class TestWifiTetherBasePreferenceController extends
+            WifiTetherBasePreferenceController<Preference> {
+
+        private String mSummary;
+        private String mDefaultSummary;
+
+        TestWifiTetherBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected Class<Preference> getPreferenceType() {
+            return Preference.class;
+        }
+
+        @Override
+        protected String getSummary() {
+            return mSummary;
+        }
+
+        @Override
+        protected String getDefaultSummary() {
+            return mDefaultSummary;
+        }
+
+        protected void setConfigSummaries(@Nullable String summary,
+                @Nullable String defaultSummary) {
+            mSummary = summary;
+            mDefaultSummary = defaultSummary;
+        }
+    }
+
+    private Context mContext;
+    private ValidatedEditTextPreference mPreference;
+    private PreferenceControllerTestHelper<TestWifiTetherBasePreferenceController>
+            mControllerHelper;
+    private TestWifiTetherBasePreferenceController mController;
+
+    @Before
+    public void setup() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new ValidatedEditTextPreference(mContext);
+        mControllerHelper =
+                new PreferenceControllerTestHelper<TestWifiTetherBasePreferenceController>(mContext,
+                        TestWifiTetherBasePreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarWifiManager.reset();
+    }
+
+    @Test
+    public void onStart_shouldStartCarWifiManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(getShadowCarWifiManager().getCurrentState())
+                .isEqualTo(getShadowCarWifiManager().STATE_STARTED);
+    }
+
+    @Test
+    public void onStop_shouldStopCarWifiManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(getShadowCarWifiManager().getCurrentState())
+                .isEqualTo(getShadowCarWifiManager().STATE_STOPPED);
+    }
+
+    @Test
+    public void onDestroy_shouldDestroyCarWifiManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        assertThat(getShadowCarWifiManager().getCurrentState())
+                .isEqualTo(getShadowCarWifiManager().STATE_DESTROYED);
+    }
+
+    @Test
+    public void noSummaryToShow_defaultSummarySet_shouldShowDefaultSummary() {
+        mController.setConfigSummaries(/* summary= */ null, DEFAULT_SUMMARY);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getSummary()).isEqualTo(DEFAULT_SUMMARY);
+    }
+
+    @Test
+    public void noSummaryToShow_defaultSummaryNotSet_shouldNotShowSummary() {
+        mController.setConfigSummaries(/* summary= */ null, /* defaultSummary= */ null);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getSummary()).isEqualTo(null);
+    }
+
+    @Test
+    public void summaryToShow_defaultSummarySet_shouldShowNonDefaultSummary() {
+        mController.setConfigSummaries(SUMMARY, DEFAULT_SUMMARY);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getSummary()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void summaryToShow_defaultSummaryNotSet_shouldSHowNonDefaultSummary() {
+        mController.setConfigSummaries(SUMMARY, /* defaultSummary= */ null);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getSummary()).isEqualTo(SUMMARY);
+    }
+
+    private ShadowCarWifiManager getShadowCarWifiManager() {
+        return Shadow.extract(new CarWifiManager(mContext));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiTetherFragmentTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherFragmentTest.java
new file mode 100644
index 0000000..e93f25f
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherFragmentTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.widget.Switch;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowCarWifiManager;
+import com.android.car.settings.testutils.ShadowConnectivityManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarWifiManager.class, ShadowConnectivityManager.class})
+public class WifiTetherFragmentTest {
+
+    private Context mContext;
+    private WifiTetherFragment mFragment;
+    private FragmentController<WifiTetherFragment> mFragmentController;
+    @Mock
+    private CarWifiManager mCarWifiManager;
+    @Mock
+    private ConnectivityManager mConnectivityManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mFragment = new WifiTetherFragment();
+        mFragmentController = FragmentController.of(mFragment);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowConnectivityManager.reset();
+        ShadowCarWifiManager.reset();
+    }
+
+    @Test
+    public void onStart_tetherStateOn_shouldReturnSwitchStateOn() {
+        when(mCarWifiManager.isWifiApEnabled()).thenReturn(true);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+
+        mFragmentController.setup();
+
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isTrue();
+    }
+
+    @Test
+    public void onStart_tetherStateOff_shouldReturnSwitchStateOff() {
+        when(mCarWifiManager.isWifiApEnabled()).thenReturn(false);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+
+        mFragmentController.setup();
+
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isFalse();
+    }
+
+    @Test
+    public void onSwitchOn_shouldAttemptTetherOn() {
+        when(mCarWifiManager.isWifiApEnabled()).thenReturn(false);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+
+        mFragmentController.setup();
+        findSwitch(mFragment.requireActivity()).performClick();
+
+        assertThat(getShadowConnectivityManager().verifyStartTetheringCalled(1)).isTrue();
+        assertThat(getShadowConnectivityManager().getTetheringType()
+                == ConnectivityManager.TETHERING_WIFI).isTrue();
+    }
+
+    @Test
+    public void onSwitchOff_shouldAttemptTetherOff() {
+        when(mCarWifiManager.isWifiApEnabled()).thenReturn(true);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+
+        mFragmentController.setup();
+        findSwitch(mFragment.requireActivity()).performClick();
+
+        assertThat(getShadowConnectivityManager().verifyStopTetheringCalled(1)).isTrue();
+        assertThat(getShadowConnectivityManager().getTetheringType()
+                == ConnectivityManager.TETHERING_WIFI).isTrue();
+    }
+
+    @Test
+    public void onTetherEnabling_shouldReturnSwitchStateDisabled() {
+        when(mCarWifiManager.isWifiApEnabled()).thenReturn(false);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+        mFragmentController.setup();
+
+        Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_ENABLING);
+        mContext.sendBroadcast(intent);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onTetherEnabled_shouldReturnSwitchStateEnabledAndOn() {
+        when(mCarWifiManager.isWifiApEnabled()).thenReturn(false);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+        mFragmentController.setup();
+
+        Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_ENABLED);
+        mContext.sendBroadcast(intent);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isTrue();
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isTrue();
+    }
+
+    @Test
+    public void onTetherDisabled_shouldReturnSwitchStateEnabledAndOff() {
+        when(mCarWifiManager.isWifiApEnabled()).thenReturn(false);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+        mFragmentController.setup();
+
+        Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_DISABLED);
+        mContext.sendBroadcast(intent);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isTrue();
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isFalse();
+    }
+
+    @Test
+    public void onEnableTetherFailed_shouldReturnSwitchStateEnabledAndOff() {
+        when(mCarWifiManager.isWifiApEnabled()).thenReturn(false);
+        ShadowCarWifiManager.setInstance(mCarWifiManager);
+        mFragmentController.setup();
+
+        Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_ENABLING);
+        mContext.sendBroadcast(intent);
+
+        Intent intent2 = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED);
+        mContext.sendBroadcast(intent2);
+
+        assertThat(findSwitch(mFragment.requireActivity()).isEnabled()).isTrue();
+        assertThat(findSwitch(mFragment.requireActivity()).isChecked()).isFalse();
+    }
+
+    private Switch findSwitch(Activity activity) {
+        return activity.findViewById(R.id.toggle_switch);
+    }
+
+    private ShadowConnectivityManager getShadowConnectivityManager() {
+        return Shadow.extract(mContext.getSystemService(Context.CONNECTIVITY_SERVICE));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiTetherNamePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherNamePreferenceControllerTest.java
new file mode 100644
index 0000000..dd42a25
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherNamePreferenceControllerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ValidatedEditTextPreference;
+import com.android.car.settings.testutils.ShadowCarWifiManager;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarWifiManager.class})
+public class WifiTetherNamePreferenceControllerTest {
+
+    private Context mContext;
+    private ValidatedEditTextPreference mPreference;
+    private PreferenceControllerTestHelper<WifiTetherNamePreferenceController> mControllerHelper;
+    private CarWifiManager mCarWifiManager;
+
+    @After
+    public void tearDown() {
+        ShadowCarWifiManager.reset();
+    }
+
+    @Test
+    public void onStart_wifiConfigHasSSID_setsSummary() {
+        mContext = RuntimeEnvironment.application;
+        mCarWifiManager = new CarWifiManager(mContext);
+        String testSSID = "TEST_SSID";
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = testSSID;
+        getShadowCarWifiManager().setWifiApConfig(config);
+        mPreference = new ValidatedEditTextPreference(mContext);
+        mControllerHelper =
+                new PreferenceControllerTestHelper<WifiTetherNamePreferenceController>(mContext,
+                        WifiTetherNamePreferenceController.class, mPreference);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(mPreference.getSummary()).isEqualTo(testSSID);
+    }
+
+    private ShadowCarWifiManager getShadowCarWifiManager() {
+        return Shadow.extract(mCarWifiManager);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceControllerTest.java
new file mode 100644
index 0000000..613525b
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceControllerTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.wifi.WifiConfiguration;
+import android.text.InputType;
+import android.text.TextUtils;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ValidatedEditTextPreference;
+import com.android.car.settings.testutils.ShadowCarWifiManager;
+import com.android.car.settings.testutils.ShadowLocalBroadcastManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarWifiManager.class, ShadowLocalBroadcastManager.class})
+public class WifiTetherPasswordPreferenceControllerTest {
+
+    private static final String TEST_PASSWORD = "TEST_PASSWORD";
+
+    private Context mContext;
+    private ValidatedEditTextPreference mPreference;
+    private PreferenceControllerTestHelper<WifiTetherPasswordPreferenceController>
+            mControllerHelper;
+    private CarWifiManager mCarWifiManager;
+    private LocalBroadcastManager mLocalBroadcastManager;
+    private WifiTetherPasswordPreferenceController mController;
+
+    @Before
+    public void setup() {
+        mContext = RuntimeEnvironment.application;
+        mCarWifiManager = new CarWifiManager(mContext);
+        mLocalBroadcastManager = LocalBroadcastManager.getInstance(mContext);
+        mPreference = new ValidatedEditTextPreference(mContext);
+        mControllerHelper =
+                new PreferenceControllerTestHelper<WifiTetherPasswordPreferenceController>(mContext,
+                        WifiTetherPasswordPreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarWifiManager.reset();
+        ShadowLocalBroadcastManager.reset();
+        SharedPreferences sp = mContext.getSharedPreferences(
+                WifiTetherPasswordPreferenceController.SHARED_PREFERENCE_PATH,
+                Context.MODE_PRIVATE);
+        sp.edit().remove(WifiTetherPasswordPreferenceController.KEY_SAVED_PASSWORD).commit();
+    }
+
+    @Test
+    public void onStart_securityTypeIsNotNone_visibilityIsSetToTrue() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = null;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void onStart_securityTypeIsNotNone_wifiConfigHasPassword_setsPasswordAsSummary() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = TEST_PASSWORD;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreference.getSummary()).isEqualTo(TEST_PASSWORD);
+    }
+
+    @Test
+    public void onStart_securityTypeIsNotNone_wifiConfigHasPassword_obscuresSummary() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = TEST_PASSWORD;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreference.getSummaryInputType())
+                .isEqualTo((InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD));
+    }
+
+    @Test
+    public void onStart_securityTypeIsNotNone_wifiConfigHasNoPassword_passwordIsNotEmpty() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = "";
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(!TextUtils.isEmpty(mPreference.getSummary())).isTrue();
+    }
+
+    @Test
+    public void onStart_securityTypeIsNotNone_wifiConfigHasNoPassword_obscuresSummary() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = "";
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mPreference.getSummaryInputType())
+                .isEqualTo((InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD));
+    }
+
+    @Test
+    public void onStart_securityTypeIsNone_visibilityIsSetToFalse() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = null;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(!mPreference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void onStart_receiverIsRegisteredOnLocalBroadcastManager() {
+        WifiConfiguration config = new WifiConfiguration();
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(
+                ShadowLocalBroadcastManager.getRegisteredBroadcastReceivers().size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void onStop_receiverIsUnregisteredFromLocalBroadcastManager() {
+        WifiConfiguration config = new WifiConfiguration();
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(
+                ShadowLocalBroadcastManager.getRegisteredBroadcastReceivers().size())
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void onSecurityChangedToNone_visibilityIsFalse() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        Intent intent = new Intent(
+                WifiTetherSecurityPreferenceController.ACTION_SECURITY_TYPE_CHANGED);
+        intent.putExtra(WifiTetherSecurityPreferenceController.KEY_SECURITY_TYPE,
+                WifiConfiguration.KeyMgmt.NONE);
+        mLocalBroadcastManager.sendBroadcast(intent);
+
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void onSecurityChangedToWPA2PSK_visibilityIsTrue() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        Intent intent = new Intent(
+                WifiTetherSecurityPreferenceController.ACTION_SECURITY_TYPE_CHANGED);
+        intent.putExtra(WifiTetherSecurityPreferenceController.KEY_SECURITY_TYPE,
+                WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mLocalBroadcastManager.sendBroadcast(intent);
+
+        assertThat(mPreference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void onChangePassword_updatesPassword() {
+        String oldPassword = "OLD_PASSWORD";
+        String newPassword = "NEW_PASSWORD";
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = oldPassword;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.handlePreferenceChanged(mPreference, newPassword);
+        String passwordReturned = mCarWifiManager.getWifiApConfig().preSharedKey;
+
+        assertThat(passwordReturned).isEqualTo(newPassword);
+    }
+
+    @Test
+    public void onChangePassword_savesNewPassword() {
+        String oldPassword = "OLD_PASSWORD";
+        String newPassword = "NEW_PASSWORD";
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = oldPassword;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+        mController.handlePreferenceChanged(mPreference, newPassword);
+
+        SharedPreferences sp = mContext.getSharedPreferences(
+                WifiTetherPasswordPreferenceController.SHARED_PREFERENCE_PATH,
+                Context.MODE_PRIVATE);
+
+        String savedPassword = sp.getString(
+                WifiTetherPasswordPreferenceController.KEY_SAVED_PASSWORD, "");
+
+        assertThat(savedPassword).isEqualTo(newPassword);
+    }
+
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/WifiTetherSecurityPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherSecurityPreferenceControllerTest.java
new file mode 100644
index 0000000..67ac93e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherSecurityPreferenceControllerTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCarWifiManager;
+import com.android.car.settings.testutils.ShadowLocalBroadcastManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarWifiManager.class, ShadowLocalBroadcastManager.class})
+public class WifiTetherSecurityPreferenceControllerTest {
+
+    private Context mContext;
+    private ListPreference mPreference;
+    private PreferenceControllerTestHelper<WifiTetherSecurityPreferenceController>
+            mControllerHelper;
+    private CarWifiManager mCarWifiManager;
+    private LocalBroadcastManager mLocalBroadcastManager;
+    private WifiTetherSecurityPreferenceController mController;
+
+    @Before
+    public void setup() {
+        mContext = RuntimeEnvironment.application;
+        mCarWifiManager = new CarWifiManager(mContext);
+        mLocalBroadcastManager = LocalBroadcastManager.getInstance(mContext);
+        mPreference = new ListPreference(mContext);
+        mControllerHelper =
+                new PreferenceControllerTestHelper<WifiTetherSecurityPreferenceController>(mContext,
+                        WifiTetherSecurityPreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarWifiManager.reset();
+        ShadowLocalBroadcastManager.reset();
+        SharedPreferences sp = mContext.getSharedPreferences(
+                WifiTetherPasswordPreferenceController.SHARED_PREFERENCE_PATH,
+                Context.MODE_PRIVATE);
+        sp.edit().remove(WifiTetherPasswordPreferenceController.KEY_SAVED_PASSWORD).commit();
+    }
+
+    @Test
+    public void onStart_securityTypeSetToNone_setsValueToNone() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = null;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(Integer.parseInt(mPreference.getValue()))
+                .isEqualTo(WifiConfiguration.KeyMgmt.NONE);
+    }
+
+    @Test
+    public void onStart_securityTypeSetToWPA2PSK_setsValueToWPA2PSK() {
+        String testPassword = "TEST_PASSWORD";
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = testPassword;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(Integer.parseInt(mPreference.getValue()))
+                .isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+    }
+
+    @Test
+    public void onPreferenceChangedToNone_updatesSecurityTypeToNone() {
+        String testPassword = "TEST_PASSWORD";
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = testPassword;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        mController.handlePreferenceChanged(mPreference,
+                Integer.toString(WifiConfiguration.KeyMgmt.NONE));
+
+        assertThat(mCarWifiManager.getWifiApConfig().getAuthType())
+                .isEqualTo(WifiConfiguration.KeyMgmt.NONE);
+
+    }
+
+    @Test
+    public void onPreferenceChangedToWPA2PSK_updatesSecurityTypeToWPA2PSK() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = null;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        mController.handlePreferenceChanged(mPreference,
+                Integer.toString(WifiConfiguration.KeyMgmt.WPA2_PSK));
+
+        assertThat(mCarWifiManager.getWifiApConfig().getAuthType())
+                .isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+    }
+
+    @Test
+    public void onPreferenceSwitchFromNoneToWPA2PSK_retrievesSavedPassword() {
+        String savedPassword = "SAVED_PASSWORD";
+        SharedPreferences sp = mContext.getSharedPreferences(
+                WifiTetherPasswordPreferenceController.SHARED_PREFERENCE_PATH,
+                Context.MODE_PRIVATE);
+        sp.edit().putString(WifiTetherPasswordPreferenceController.KEY_SAVED_PASSWORD,
+                savedPassword).commit();
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.preSharedKey = null;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        mController.handlePreferenceChanged(mPreference,
+                Integer.toString(WifiConfiguration.KeyMgmt.WPA2_PSK));
+
+        assertThat(mCarWifiManager.getWifiApConfig().preSharedKey).isEqualTo(savedPassword);
+    }
+
+    @Test
+    public void onPreferenceChanged_broadcastsExactlyOneIntent() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        int newSecurityType = WifiConfiguration.KeyMgmt.WPA2_PSK;
+        mController.handlePreferenceChanged(mPreference, newSecurityType);
+
+        assertThat(ShadowLocalBroadcastManager.getSentBroadcastIntents().size()).isEqualTo(1);
+    }
+
+    @Test
+    public void onPreferenceChangedToWPA2PSK_broadcastsSecurityTypeWPA2PSK() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        int newSecurityType = WifiConfiguration.KeyMgmt.WPA2_PSK;
+
+        mController.handlePreferenceChanged(mPreference, newSecurityType);
+
+        Intent expectedIntent = new Intent(
+                WifiTetherSecurityPreferenceController.ACTION_SECURITY_TYPE_CHANGED);
+        expectedIntent.putExtra(WifiTetherSecurityPreferenceController.KEY_SECURITY_TYPE,
+                newSecurityType);
+
+        assertThat(
+                ShadowLocalBroadcastManager.getSentBroadcastIntents().get(0).toString())
+                .isEqualTo(expectedIntent.toString());
+    }
+
+    @Test
+    public void onPreferenceChangedToNone_broadcastsSecurityTypeNone() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        int newSecurityType = WifiConfiguration.KeyMgmt.NONE;
+
+        mController.handlePreferenceChanged(mPreference, newSecurityType);
+
+        Intent expectedIntent = new Intent(
+                WifiTetherSecurityPreferenceController.ACTION_SECURITY_TYPE_CHANGED);
+        expectedIntent.putExtra(WifiTetherSecurityPreferenceController.KEY_SECURITY_TYPE,
+                newSecurityType);
+
+        assertThat(
+                ShadowLocalBroadcastManager.getSentBroadcastIntents().get(0).toString())
+                .isEqualTo(expectedIntent.toString());
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/details/WifiFrequencyPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/details/WifiFrequencyPreferenceControllerTest.java
new file mode 100644
index 0000000..89a6b6a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/details/WifiFrequencyPreferenceControllerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiFrequencyPreferenceControllerTest {
+
+    @Mock
+    private AccessPoint mMockAccessPoint;
+    @Mock
+    private WifiInfoProvider mMockWifiInfoProvider;
+    @Mock
+    private NetworkInfo mMockNetworkInfo;
+    @Mock
+    private WifiInfo mMockWifiInfo;
+
+    private WifiDetailsPreference mPreference;
+    private Context mContext;
+    private WifiFrequencyPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        shadowOf(mContext.getPackageManager()).setSystemFeature(PackageManager.FEATURE_WIFI, true);
+        mPreference = new WifiDetailsPreference(mContext);
+
+        PreferenceControllerTestHelper<WifiFrequencyPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        WifiFrequencyPreferenceController.class, mPreference);
+        mController = (WifiFrequencyPreferenceController) controllerHelper.getController().init(
+                mMockAccessPoint, mMockWifiInfoProvider);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        when(mMockWifiInfoProvider.getWifiInfo()).thenReturn(mMockWifiInfo);
+        when(mMockWifiInfo.getFrequency()).thenReturn(AccessPoint.LOWER_FREQ_24GHZ);
+    }
+
+    @Test
+    public void onWifiChanged_shouldHaveDetailTextSet() {
+        when(mMockAccessPoint.isActive()).thenReturn(true);
+        when(mMockWifiInfo.getFrequency()).thenReturn(AccessPoint.LOWER_FREQ_5GHZ);
+
+        String expected = mContext.getResources().getString(R.string.wifi_band_5ghz);
+        mController.onWifiChanged(mMockNetworkInfo, mMockWifiInfo);
+        assertThat(mPreference.getDetailText()).isEqualTo(expected);
+    }
+
+    @Test
+    public void onWifiChanged_isNotActive_noUpdate() {
+        when(mMockAccessPoint.isActive()).thenReturn(false);
+
+        mController.onWifiChanged(mMockNetworkInfo, mMockWifiInfo);
+        assertThat(mPreference.getDetailText()).isNull();
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/car/settings/wifi/details/WifiGatewayPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/details/WifiGatewayPreferenceControllerTest.java
new file mode 100644
index 0000000..dc54df2
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/details/WifiGatewayPreferenceControllerTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.RouteInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiGatewayPreferenceControllerTest {
+
+    private static final String GATE_WAY = "gateway";
+
+    @Mock
+    private AccessPoint mMockAccessPoint;
+    @Mock
+    private WifiInfoProvider mMockWifiInfoProvider;
+    @Mock
+    private Network mMockNetwork;
+    @Mock
+    private LinkProperties mMockLinkProperties;
+    @Mock
+    private RouteInfo mMockRouteInfo;
+    @Mock
+    private InetAddress mMockInetAddress;
+
+    private Context mContext;
+    private WifiDetailsPreference mPreference;
+    private WifiGatewayPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        shadowOf(mContext.getPackageManager()).setSystemFeature(PackageManager.FEATURE_WIFI, true);
+        mPreference = new WifiDetailsPreference(mContext);
+        when(mMockWifiInfoProvider.getLinkProperties()).thenReturn(mMockLinkProperties);
+
+        PreferenceControllerTestHelper<WifiGatewayPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        WifiGatewayPreferenceController.class, mPreference);
+        mController = (WifiGatewayPreferenceController) controllerHelper.getController().init(
+                mMockAccessPoint, mMockWifiInfoProvider);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void onWifiChanged_shouldHaveDetailTextSet() {
+        when(mMockAccessPoint.isActive()).thenReturn(true);
+        when(mMockLinkProperties.getRoutes()).thenReturn(Arrays.asList(mMockRouteInfo));
+        when(mMockRouteInfo.isIPv4Default()).thenReturn(true);
+        when(mMockRouteInfo.hasGateway()).thenReturn(true);
+        when(mMockRouteInfo.getGateway()).thenReturn(mMockInetAddress);
+        when(mMockInetAddress.getHostAddress()).thenReturn(GATE_WAY);
+
+        mController.onLinkPropertiesChanged(mMockNetwork, mMockLinkProperties);
+        assertThat(mPreference.getDetailText()).isEqualTo(GATE_WAY);
+    }
+
+    @Test
+    public void onWifiChanged_isNotActive_noUpdate() {
+        when(mMockAccessPoint.isActive()).thenReturn(false);
+
+        mController.onLinkPropertiesChanged(mMockNetwork, mMockLinkProperties);
+        assertThat(mPreference.getDetailText()).isNull();
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/car/settings/wifi/details/WifiInfoProviderImplTest.java b/tests/robotests/src/com/android/car/settings/wifi/details/WifiInfoProviderImplTest.java
new file mode 100644
index 0000000..9928f52
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/details/WifiInfoProviderImplTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+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.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.wifi.details.WifiInfoProvider.Listener;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiInfoProviderImplTest {
+    private static final int LEVEL = 1;
+    private static final int RSSI = -55;
+    private static final int LINK_SPEED = 123;
+    private static final String MAC_ADDRESS = WifiInfo.DEFAULT_MAC_ADDRESS;
+    private static final String SECURITY = "None";
+
+    @Mock
+    private AccessPoint mMockAccessPoint;
+    @Mock
+    private ConnectivityManager mMockConnectivityManager;
+    @Mock
+    private Network mMockNetwork;
+    @Mock
+    private NetworkInfo mMockNetworkInfo;
+    @Mock
+    private NetworkInfo mMockNetworkInfo2;
+    @Mock
+    private WifiConfiguration mMockWifiConfig;
+    @Mock
+    private WifiInfo mMockWifiInfo;
+    @Mock
+    private WifiInfo mMockWifiInfo2;
+    @Mock
+    private LinkProperties mMockLinkProperties;
+    @Mock
+    private LinkProperties mMockChangedLinkProperties;
+    @Mock
+    private NetworkCapabilities mMockNetworkCapabilities;
+    @Mock
+    private NetworkCapabilities mMockChangedNetworkCapabilities;
+    @Mock
+    private WifiManager mMockWifiManager;
+    @Mock
+    private Listener mMockListener;
+
+    @Captor
+    private ArgumentCaptor<NetworkCallback> mCallbackCaptor;
+
+    private Context mContext;
+    private WifiInfoProviderImpl mWifiInfoProviderImpl;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(RuntimeEnvironment.application);
+        when(mContext.getSystemService(ConnectivityManager.class))
+                .thenReturn(mMockConnectivityManager);
+        when(mContext.getSystemService(WifiManager.class)).thenReturn(mMockWifiManager);
+        when(mMockAccessPoint.getConfig()).thenReturn(mMockWifiConfig);
+        when(mMockAccessPoint.getLevel()).thenReturn(LEVEL);
+        when(mMockAccessPoint.getSecurityString(false)).thenReturn(SECURITY);
+        when(mMockConnectivityManager.getNetworkInfo(any(Network.class)))
+                .thenReturn(mMockNetworkInfo);
+        when(mMockConnectivityManager.getLinkProperties(any(Network.class)))
+                .thenReturn(mMockLinkProperties);
+        when(mMockConnectivityManager.getNetworkCapabilities(any(Network.class)))
+                .thenReturn(mMockNetworkCapabilities);
+        doNothing().when(mMockConnectivityManager).registerNetworkCallback(
+                nullable(NetworkRequest.class), mCallbackCaptor.capture(), nullable(Handler.class));
+        when(mMockWifiInfo.getLinkSpeed()).thenReturn(LINK_SPEED);
+        when(mMockWifiInfo.getRssi()).thenReturn(RSSI);
+        when(mMockWifiInfo.getMacAddress()).thenReturn(MAC_ADDRESS);
+        when(mMockWifiManager.getConnectionInfo()).thenReturn(mMockWifiInfo);
+
+        when(mMockWifiManager.getCurrentNetwork()).thenReturn(mMockNetwork);
+
+        mWifiInfoProviderImpl = new WifiInfoProviderImpl(mContext, mMockAccessPoint);
+        mWifiInfoProviderImpl.addListener(mMockListener);
+    }
+
+    @Test
+    public void onStart_allFieldsInitialized() {
+        mWifiInfoProviderImpl.onStart();
+
+        assertThat(mWifiInfoProviderImpl.getNetworkInfo()).isNotNull();
+        assertThat(mWifiInfoProviderImpl.getWifiInfo()).isNotNull();
+        assertThat(mWifiInfoProviderImpl.getNetwork()).isNotNull();
+        assertThat(mWifiInfoProviderImpl.getNetworkCapabilities()).isNotNull();
+        assertThat(mWifiInfoProviderImpl.getNetworkConfiguration()).isNotNull();
+        assertThat(mWifiInfoProviderImpl.getLinkProperties()).isNotNull();
+    }
+
+    @Test
+    public void onStart_listenerCallback() {
+        mWifiInfoProviderImpl.onStart();
+        verify(mMockListener).onWifiChanged(eq(mMockNetworkInfo), eq(mMockWifiInfo));
+    }
+
+    @Test
+    public void onStart_getsNetwork() {
+        mWifiInfoProviderImpl.onStart();
+        assertThat(mWifiInfoProviderImpl.getNetwork()).isEqualTo(mMockNetwork);
+    }
+
+    @Test
+    public void networkCallback_shouldBeRegisteredOnStart() {
+        mWifiInfoProviderImpl.onStart();
+
+        verify(mMockConnectivityManager).registerNetworkCallback(
+                nullable(NetworkRequest.class), mCallbackCaptor.capture(), nullable(Handler.class));
+    }
+
+    @Test
+    public void networkCallback_shouldBeUnregisteredOnStop() {
+        mWifiInfoProviderImpl.onStart();
+        mWifiInfoProviderImpl.onStop();
+
+        verify(mMockConnectivityManager)
+                .unregisterNetworkCallback(mCallbackCaptor.getValue());
+    }
+
+    @Test
+    public void networkStateChangedIntent_shouldRefetchInfo() {
+        mWifiInfoProviderImpl.onStart();
+
+        assertThat(mWifiInfoProviderImpl.getNetwork()).isEqualTo(mMockNetwork);
+        assertThat(mWifiInfoProviderImpl.getWifiInfo()).isEqualTo(mMockWifiInfo);
+        assertThat(mWifiInfoProviderImpl.getNetworkInfo()).isEqualTo(mMockNetworkInfo);
+
+        when(mMockWifiManager.getConnectionInfo()).thenReturn(mMockWifiInfo2);
+        when(mMockConnectivityManager.getNetworkInfo(any(Network.class)))
+                .thenReturn(mMockNetworkInfo2);
+
+        mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
+
+        assertThat(mWifiInfoProviderImpl.getNetwork()).isEqualTo(mMockNetwork);
+        assertThat(mWifiInfoProviderImpl.getWifiInfo()).isEqualTo(mMockWifiInfo2);
+        assertThat(mWifiInfoProviderImpl.getNetworkInfo()).isEqualTo(mMockNetworkInfo2);
+    }
+
+    @Test
+    public void rssiChangedIntent_shouldRefetchInfo() {
+        mWifiInfoProviderImpl.onStart();
+
+        assertThat(mWifiInfoProviderImpl.getNetwork()).isEqualTo(mMockNetwork);
+        assertThat(mWifiInfoProviderImpl.getWifiInfo()).isEqualTo(mMockWifiInfo);
+        assertThat(mWifiInfoProviderImpl.getNetworkInfo()).isEqualTo(mMockNetworkInfo);
+
+        when(mMockWifiManager.getConnectionInfo()).thenReturn(mMockWifiInfo2);
+        when(mMockConnectivityManager.getNetworkInfo(any(Network.class)))
+                .thenReturn(mMockNetworkInfo2);
+
+        mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION));
+
+        assertThat(mWifiInfoProviderImpl.getNetwork()).isEqualTo(mMockNetwork);
+        assertThat(mWifiInfoProviderImpl.getWifiInfo()).isEqualTo(mMockWifiInfo2);
+        assertThat(mWifiInfoProviderImpl.getNetworkInfo()).isEqualTo(mMockNetworkInfo2);
+    }
+
+    @Test
+    public void onLost_lisntenerCallback() {
+        mWifiInfoProviderImpl.onStart();
+
+        mCallbackCaptor.getValue().onLost(mMockNetwork);
+
+        verify(mMockListener).onLost(any(Network.class));
+    }
+
+    @Test
+    public void onLinkPropertiesChanged_lisntenerCallback() {
+        mWifiInfoProviderImpl.onStart();
+
+        mCallbackCaptor.getValue().onLinkPropertiesChanged(
+                mMockNetwork, mMockChangedLinkProperties);
+
+        verify(mMockListener).onLinkPropertiesChanged(
+                any(Network.class), eq(mMockChangedLinkProperties));
+    }
+
+    @Test
+    public void onCapabilitiesChanged_lisntenerCallback() {
+        mWifiInfoProviderImpl.onStart();
+
+        mCallbackCaptor.getValue().onCapabilitiesChanged(
+                mMockNetwork, mMockChangedNetworkCapabilities);
+
+        verify(mMockListener).onCapabilitiesChanged(
+                any(Network.class), eq(mMockChangedNetworkCapabilities));
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/details/WifiMacAddressPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/details/WifiMacAddressPreferenceControllerTest.java
new file mode 100644
index 0000000..7fb3b48
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/details/WifiMacAddressPreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiMacAddressPreferenceControllerTest {
+
+    private static final String MAC_ADDRESS = "mac address";
+
+    @Mock
+    private AccessPoint mMockAccessPoint;
+    @Mock
+    private WifiInfoProvider mMockWifiInfoProvider;
+    @Mock
+    private NetworkInfo mMockNetworkInfo;
+    @Mock
+    private WifiInfo mMockWifiInfo;
+
+    private Context mContext;
+    private WifiDetailsPreference mPreference;
+    private WifiMacAddressPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        shadowOf(mContext.getPackageManager()).setSystemFeature(PackageManager.FEATURE_WIFI, true);
+        mPreference = new WifiDetailsPreference(mContext);
+        when(mMockWifiInfoProvider.getWifiInfo()).thenReturn(mMockWifiInfo);
+
+        PreferenceControllerTestHelper<WifiMacAddressPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        WifiMacAddressPreferenceController.class, mPreference);
+        mController = (WifiMacAddressPreferenceController) controllerHelper.getController().init(
+                mMockAccessPoint, mMockWifiInfoProvider);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void onWifiChanged_shouldHaveDetailTextSet() {
+        when(mMockAccessPoint.isActive()).thenReturn(true);
+        when(mMockWifiInfo.getMacAddress()).thenReturn(MAC_ADDRESS);
+
+        mController.onWifiChanged(mMockNetworkInfo, mMockWifiInfo);
+        assertThat(mPreference.getDetailText()).isEqualTo(MAC_ADDRESS);
+    }
+
+    @Test
+    public void onWifiChanged_isNotActive_noUpdate() {
+        when(mMockAccessPoint.isActive()).thenReturn(false);
+
+        mController.onWifiChanged(mMockNetworkInfo, mMockWifiInfo);
+        assertThat(mPreference.getDetailText()).isNull();
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/car/settings/wifi/details/WifiSignalStrengthPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/details/WifiSignalStrengthPreferenceControllerTest.java
new file mode 100644
index 0000000..fbe4ac7
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/details/WifiSignalStrengthPreferenceControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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.car.settings.wifi.details;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.settingslib.wifi.AccessPoint;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class WifiSignalStrengthPreferenceControllerTest {
+
+    private static final int LEVEL = 1;
+
+    @Mock
+    private AccessPoint mMockAccessPoint;
+    @Mock
+    private WifiInfoProvider mMockWifiInfoProvider;
+    @Mock
+    private NetworkInfo mMockNetworkInfo;
+    @Mock
+    private WifiInfo mMockWifiInfo;
+
+    private WifiDetailsPreference mWifiDetailPreference;
+    private Context mContext;
+    private WifiSignalStrengthPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        shadowOf(mContext.getPackageManager()).setSystemFeature(PackageManager.FEATURE_WIFI, true);
+
+        mWifiDetailPreference = new WifiDetailsPreference(mContext);
+        when(mMockAccessPoint.getLevel()).thenReturn(LEVEL);
+
+        PreferenceControllerTestHelper<WifiSignalStrengthPreferenceController> controllerHelper =
+                new PreferenceControllerTestHelper<>(mContext,
+                        WifiSignalStrengthPreferenceController.class, mWifiDetailPreference);
+        mController =
+                (WifiSignalStrengthPreferenceController) controllerHelper.getController().init(
+                        mMockAccessPoint, mMockWifiInfoProvider);
+        controllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Test
+    public void onWifiChanged_shouldHaveDetailTextSet() {
+        when(mMockAccessPoint.isActive()).thenReturn(true);
+        String expectedStrength =
+                mContext.getResources().getStringArray(R.array.wifi_signals)[LEVEL];
+
+        mController.onWifiChanged(mMockNetworkInfo, mMockWifiInfo);
+        assertThat(mWifiDetailPreference.getDetailText()).isEqualTo(expectedStrength);
+    }
+
+    @Test
+    public void onWifiChanged_isNotActive_noUpdate() {
+        when(mMockAccessPoint.isActive()).thenReturn(false);
+
+        mController.onWifiChanged(mMockNetworkInfo, mMockWifiInfo);
+        assertThat(mWifiDetailPreference.getDetailText()).isNull();
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/car/settings/wifi/preferences/CellularFallbackTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/preferences/CellularFallbackTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..f9bc2ab
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/preferences/CellularFallbackTogglePreferenceControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi.preferences;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class CellularFallbackTogglePreferenceControllerTest {
+
+    private Context mContext;
+    private SwitchPreference mPreference;
+    private PreferenceControllerTestHelper<CellularFallbackTogglePreferenceController>
+            mPreferenceControllerHelper;
+    private CellularFallbackTogglePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+
+        mPreference = new SwitchPreference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                CellularFallbackTogglePreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @Test
+    public void refreshUi_unchecked() {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
+        mPreference.setChecked(true);
+
+        mController.refreshUi();
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_checked() {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NETWORK_AVOID_BAD_WIFI, "1");
+        mPreference.setChecked(false);
+
+        mController.refreshUi();
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void handlePreferenceChanged_toggleFalse_setsNull() {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NETWORK_AVOID_BAD_WIFI, "1");
+
+        mPreference.callChangeListener(false);
+        assertThat(Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.NETWORK_AVOID_BAD_WIFI)).isNull();
+    }
+
+    @Test
+    public void handlePreferenceChanged_toggleTrue_setsEnabled() {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
+
+        mPreference.callChangeListener(true);
+        assertThat(Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.NETWORK_AVOID_BAD_WIFI)).isEqualTo("1");
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/preferences/ConfirmEnableWifiScanningDialogFragmentTest.java b/tests/robotests/src/com/android/car/settings/wifi/preferences/ConfirmEnableWifiScanningDialogFragmentTest.java
new file mode 100644
index 0000000..cb331a5
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/preferences/ConfirmEnableWifiScanningDialogFragmentTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi.preferences;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.BaseTestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.shadows.ShadowDialog;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ConfirmEnableWifiScanningDialogFragmentTest {
+
+    private ConfirmEnableWifiScanningDialogFragment mFragment;
+    @Mock
+    private ConfirmEnableWifiScanningDialogFragment.WifiScanningEnabledListener mListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mFragment = new ConfirmEnableWifiScanningDialogFragment();
+        mFragment.setWifiScanningEnabledListener(mListener);
+    }
+
+    @Test
+    public void onClick_positiveButton_callsListener() {
+        AlertDialog dialog = showDialog(mFragment);
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        verify(mListener).onWifiScanningEnabled();
+    }
+
+    private AlertDialog showDialog(ConfirmEnableWifiScanningDialogFragment fragment) {
+        BaseTestActivity activity = Robolectric.setupActivity(BaseTestActivity.class);
+        activity.showDialog(fragment, /* tag= */ null);
+        return (AlertDialog) ShadowDialog.getLatestDialog();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/wifi/preferences/WifiWakeupTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/preferences/WifiWakeupTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..6a4a2e4
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/wifi/preferences/WifiWakeupTogglePreferenceControllerTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.wifi.preferences;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.location.LocationManager;
+import android.os.Process;
+import android.provider.Settings;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowLocationManager;
+import com.android.car.settings.testutils.ShadowSecureSettings;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowToast;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowSecureSettings.class, ShadowLocationManager.class})
+public class WifiWakeupTogglePreferenceControllerTest {
+
+    private Context mContext;
+    private PreferenceControllerTestHelper<WifiWakeupTogglePreferenceController> mControllerHelper;
+    private WifiWakeupTogglePreferenceController mController;
+    private TwoStatePreference mTwoStatePreference;
+    private LocationManager mLocationManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mLocationManager = (LocationManager) mContext.getSystemService(Service.LOCATION_SERVICE);
+        mTwoStatePreference = new SwitchPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                WifiWakeupTogglePreferenceController.class, mTwoStatePreference);
+        mController = mControllerHelper.getController();
+
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSecureSettings.reset();
+    }
+
+    @Test
+    public void handlePreferenceClicked_locationDisabled_startNewActivity() {
+        setLocationEnabled(false);
+
+        mTwoStatePreference.performClick();
+
+        Intent actual = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(actual.getAction()).isEqualTo(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+    }
+
+    @Test
+    public void handlePreferenceClicked_wifiWakeupEnabled_disablesWifiWakeup() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                1);
+
+        mTwoStatePreference.performClick();
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_WAKEUP_ENABLED, 1))
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void handlePreferenceClicked_wifiScanningDisabled_showsDialog() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                0);
+
+        mTwoStatePreference.performClick();
+
+        verify(mControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmEnableWifiScanningDialogFragment.class),
+                eq(ConfirmEnableWifiScanningDialogFragment.TAG));
+    }
+
+    @Test
+    public void handlePreferenceClicked_wifiScanningEnabled_wifiWakeupDisabled_enablesWifiWakeup() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                0);
+
+        mTwoStatePreference.performClick();
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_WAKEUP_ENABLED, 0))
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void refreshUi_wifiWakeupEnabled_wifiScanningEnabled_locationEnabled_isChecked() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                1);
+        mTwoStatePreference.setChecked(false);
+
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_wifiWakeupDisabled_wifiScanningEnabled_locationEnabled_isNotChecked() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                0);
+        mTwoStatePreference.setChecked(true);
+
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_wifiWakeupEnabled_wifiScanningDisabled_locationEnabled_isNotChecked() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                1);
+        mTwoStatePreference.setChecked(true);
+
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void refreshUi_wifiWakeupEnabled_wifiScanningEnabled_locationDisabled_isNotChecked() {
+        setLocationEnabled(false);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                1);
+        mTwoStatePreference.setChecked(true);
+
+        mController.refreshUi();
+
+        assertThat(mTwoStatePreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onWifiScanningEnabled_setsWifiScanningOn() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                0);
+
+        mController.onWifiScanningEnabled();
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0)).isEqualTo(1);
+    }
+
+    @Test
+    public void onWifiScanningEnabled_showsToast() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                0);
+
+        mController.onWifiScanningEnabled();
+
+        assertThat(ShadowToast.showedToast(
+                mContext.getString(R.string.wifi_settings_scanning_required_enabled))).isTrue();
+    }
+
+    @Test
+    public void onWifiScanningEnabled_enablesWifiWakeup() {
+        setLocationEnabled(true);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
+                0);
+
+        mController.onWifiScanningEnabled();
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_WAKEUP_ENABLED, 0)).isEqualTo(1);
+    }
+
+    private void setLocationEnabled(boolean enabled) {
+        mLocationManager.setLocationEnabledForUser(enabled, Process.myUserHandle());
+    }
+}