Merge "Rework Add Wifi page for cleaner code" into pi-car-dev
diff --git a/Android.mk b/Android.mk
index b27b4d8..1fd2fc6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -40,6 +40,7 @@
       car-apps-common \
       car-settings-lib \
       car-setup-wizard-lib \
+      car-setup-wizard-lib-utils \
       setup-wizard-lib-gingerbread-compat \
       SettingsLib
 
@@ -99,6 +100,7 @@
       car-apps-common \
       car-settings-lib \
       car-setup-wizard-lib \
+      car-setup-wizard-lib-utils \
       setup-wizard-lib-gingerbread-compat \
       SettingsLib
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f4d2a8e..ee56b77 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,6 +61,9 @@
     <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"
@@ -307,6 +310,10 @@
 
         <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" />
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
index a97df25..7a34f12 100644
--- a/res/values/preference_keys.xml
+++ b/res/values/preference_keys.xml
@@ -64,8 +64,9 @@
     <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_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>
@@ -169,9 +170,24 @@
     </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_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_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>
diff --git a/res/values/string_arrays.xml b/res/values/string_arrays.xml
index 8626d42..50028ad 100644
--- a/res/values/string_arrays.xml
+++ b/res/values/string_arrays.xml
@@ -21,4 +21,37 @@
         <item>@string/wifi_security_wep</item>
         <item>@string/wifi_security_wpa_wpa2</item>
     </string-array>
-</resources>
+
+    <!-- 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 2d349bc..c5e510d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -223,8 +223,30 @@
     <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]-->
@@ -368,6 +390,8 @@
     <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] -->
@@ -455,6 +479,14 @@
 
     <!-- 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=30] -->
+    <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] -->
@@ -467,6 +499,14 @@
     <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 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 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 allow 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>
 
     <!-- Location --><skip/>
     <!-- Main setting menu item to go into location settings [CHAR LIMIT=40] -->
diff --git a/res/xml/homepage_fragment.xml b/res/xml/homepage_fragment.xml
index 3eebf50..10e3e2d 100644
--- a/res/xml/homepage_fragment.xml
+++ b/res/xml/homepage_fragment.xml
@@ -19,9 +19,11 @@
     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"
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/special_access_fragment.xml b/res/xml/special_access_fragment.xml
index 2cde396..33c1706 100644
--- a/res/xml/special_access_fragment.xml
+++ b/res/xml/special_access_fragment.xml
@@ -20,8 +20,23 @@
     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.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.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/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/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_tether_fragment.xml b/res/xml/wifi_tether_fragment.xml
index df9bb83..a0d1bcf 100644
--- a/res/xml/wifi_tether_fragment.xml
+++ b/res/xml/wifi_tether_fragment.xml
@@ -21,13 +21,27 @@
         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/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..a3fea69
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceController.java
@@ -0,0 +1,292 @@
+/*
+ * 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.app.Application;
+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.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.ArrayList;
+
+/**
+ * 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 ApplicationsState mApplicationsState;
+
+    private final ApplicationsState.Callbacks mCallbacks =
+            new ApplicationsState.Callbacks() {
+                @Override
+                public void onRunningStateChanged(boolean running) {
+                    // TODO: show loading UI.
+                }
+
+                @Override
+                public void onPackageListChanged() {
+                    rebuild();
+                }
+
+                @Override
+                public void onRebuildComplete(ArrayList<AppEntry> entries) {
+                    mEntries = entries;
+                    refreshUi();
+                }
+
+                @Override
+                public void onPackageIconChanged() {
+                    // No op.
+                }
+
+                @Override
+                public void onPackageSizeChanged(String packageName) {
+                    // No op.
+                }
+
+                @Override
+                public void onAllSizesComputed() {
+                    // No op.
+                }
+
+                @Override
+                public void onLauncherInfoChanged() {
+                    rebuild();
+                }
+
+                @Override
+                public void onLoadEntriesCompleted() {
+                    mHasReceivedLoadEntries = true;
+                    rebuild();
+                }
+            };
+
+    private final AppStateBaseBridge.Callback mBridgeCallback = new AppStateBaseBridge.Callback() {
+        @Override
+        public void onExtraInfoUpdated() {
+            mHasReceivedBridgeCallback = true;
+            rebuild();
+        }
+    };
+
+    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.
+                        mExtraInfoBridge.forceUpdate(entry);
+                        return true;
+                    }
+                    return false;
+                }
+            };
+
+    private int mAppOpsOpCode = AppOpsManager.OP_NONE;
+    private String mPermission;
+    private int mNegativeOpMode = -1;
+
+    private ApplicationsState.Session mSession;
+    private AppStateAppOpsBridge mExtraInfoBridge;
+
+    private ArrayList<AppEntry> mEntries;
+
+    private boolean mHasReceivedLoadEntries;
+    private boolean mHasReceivedBridgeCallback;
+
+    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);
+        mApplicationsState = ApplicationsState.getInstance(
+                (Application) context.getApplicationContext());
+    }
+
+    @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;
+            rebuild();
+        }
+    }
+
+    @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() {
+        mSession = mApplicationsState.newSession(mCallbacks);
+        mExtraInfoBridge = new AppStateAppOpsBridge(getContext(), mApplicationsState, mAppOpsOpCode,
+                mPermission, mBridgeCallback);
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mSession.onResume();
+        mExtraInfoBridge.start();
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mSession.onPause();
+        mExtraInfoBridge.stop();
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mSession.onDestroy();
+        mExtraInfoBridge.destroy();
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        if (mEntries == null) {
+            // Still loading.
+            return;
+        }
+        preference.removeAll();
+        for (AppEntry entry : mEntries) {
+            mApplicationsState.ensureIcon(entry);
+            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 void rebuild() {
+        if (!mHasReceivedLoadEntries || !mHasReceivedBridgeCallback) {
+            // Don't rebuild the list until all the app entries are loaded.
+            return;
+        }
+        mSession.rebuild(getAppFilter(), ApplicationsState.ALPHA_COMPARATOR, /* foreground= */
+                false);
+    }
+
+    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/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/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/home/HomepageFragment.java b/src/com/android/car/settings/home/HomepageFragment.java
index 2e11e05..9ca03ae 100644
--- a/src/com/android/car/settings/home/HomepageFragment.java
+++ b/src/com/android/car/settings/home/HomepageFragment.java
@@ -19,11 +19,9 @@
 import android.content.Context;
 
 import androidx.annotation.XmlRes;
-import androidx.loader.app.LoaderManager;
 
 import com.android.car.settings.R;
 import com.android.car.settings.common.SettingsFragment;
-import com.android.car.settings.suggestions.SuggestionsPreferenceController;
 
 /**
  * Homepage for settings for car.
@@ -39,7 +37,8 @@
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
-        use(SuggestionsPreferenceController.class, R.string.pk_suggestions).setLoaderManager(
-                LoaderManager.getInstance(/* owner= */ this));
+        // 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/security/PasswordHelper.java b/src/com/android/car/settings/security/PasswordHelper.java
index 856d42f..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,22 +32,20 @@
  */
 public class PasswordHelper {
     public static final String EXTRA_CURRENT_SCREEN_LOCK = "extra_current_screen_lock";
-
     /**
      * 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;
     }
 
@@ -70,9 +70,21 @@
     }
 
     /**
+     * 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(byte[]) validatePassword}
      */
     public List<String> convertErrorCodeToMessages(Context context, int errorCode) {
@@ -99,6 +111,27 @@
         return errorCode;
     }
 
+    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;
         PasswordMetrics metrics = PasswordMetrics.computeForPassword(pin);
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/wifi/CarWifiManager.java b/src/com/android/car/settings/wifi/CarWifiManager.java
index 096d409..6f4a9f8 100644
--- a/src/com/android/car/settings/wifi/CarWifiManager.java
+++ b/src/com/android/car/settings/wifi/CarWifiManager.java
@@ -185,6 +185,28 @@
         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();
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/WifiTetherBasePreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherBasePreferenceController.java
index 8c6dd97..dd74f4b 100644
--- a/src/com/android/car/settings/wifi/WifiTetherBasePreferenceController.java
+++ b/src/com/android/car/settings/wifi/WifiTetherBasePreferenceController.java
@@ -89,6 +89,10 @@
         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/WifiTetherPasswordPreferenceController.java b/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceController.java
index b634483..14f83e0 100644
--- a/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceController.java
+++ b/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceController.java
@@ -17,15 +17,23 @@
 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.
  *
@@ -36,13 +44,31 @@
 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) {
@@ -57,8 +83,23 @@
     @Override
     protected void onCreateInternal() {
         super.onCreateInternal();
+
         getPreference().setValidator(PASSWORD_VALIDATOR);
-        mPassword = getCarWifiApConfig().preSharedKey;
+        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
@@ -73,7 +114,7 @@
     @Override
     protected void updateState(ValidatedEditTextPreference preference) {
         super.updateState(preference);
-        preference.setText(mPassword);
+        updatePasswordDisplay();
         if (TextUtils.isEmpty(mPassword)) {
             preference.setSummaryInputType(InputType.TYPE_CLASS_TEXT);
         } else {
@@ -92,9 +133,49 @@
         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/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/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/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java b/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
index a5ac933..ba9b84d 100644
--- a/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
+++ b/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
@@ -101,7 +101,8 @@
 
             // 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.fromURL(manifestPath), Fs.fromURL(resDir),
+            AndroidManifest androidManifest = new AndroidManifest(Fs.fromURL(manifestPath),
+                    Fs.fromURL(resDir),
                     Fs.fromURL(assetsDir)) {
                 @Override
                 public List<ResourcePath> getIncludedResourcePaths() {
@@ -123,6 +124,10 @@
                     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/applications/specialaccess/AppOpsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceControllerTest.java
new file mode 100644
index 0000000..497bd95
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppOpsPreferenceControllerTest.java
@@ -0,0 +1,375 @@
+/*
+ * 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.times;
+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.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+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.ShadowActivityThread;
+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.AdditionalMatchers;
+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.ArrayList;
+import java.util.Collections;
+
+/** Unit test for {@link AppOpsPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowAppOpsManager.class, ShadowApplicationsState.class,
+        ShadowActivityThread.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 IPackageManager mIPackageManager;
+    @Mock
+    private ParceledListSlice<PackageInfo> mParceledPackages;
+    @Mock
+    private ApplicationsState mApplicationsState;
+    @Mock
+    private ApplicationsState.Session mSession;
+    @Mock
+    private ApplicationsState.Session mBridgeSession;
+    @Captor
+    private ArgumentCaptor<ApplicationsState.Callbacks> mCallbackCaptor;
+
+    private Context mContext;
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<AppOpsPreferenceController> mControllerHelper;
+    private AppOpsPreferenceController mController;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        ShadowActivityThread.setPackageManager(mIPackageManager);
+        when(mIPackageManager.getPackagesHoldingPermissions(
+                AdditionalMatchers.aryEq(new String[]{PERMISSION}),
+                eq(PackageManager.GET_PERMISSIONS),
+                eq(UserHandle.myUserId())))
+                .thenReturn(mParceledPackages);
+        when(mParceledPackages.getList()).thenReturn(Collections.emptyList());
+        ShadowApplicationsState.setInstance(mApplicationsState);
+        when(mApplicationsState.newSession(mCallbackCaptor.capture()))
+                .thenReturn(mSession)
+                .thenReturn(mBridgeSession);
+        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);
+        mControllerHelper.setPreference(mPreferenceGroup);
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationsState.reset();
+        ShadowActivityThread.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_resumesSessions() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mSession).onResume();
+        verify(mBridgeSession).onResume();
+    }
+
+    @Test
+    public void onStop_pausesSessions() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mSession).onPause();
+        verify(mBridgeSession).onPause();
+    }
+
+    @Test
+    public void onDestroy_destroysSessions() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        verify(mSession).onDestroy();
+        verify(mBridgeSession).onDestroy();
+    }
+
+    @Test
+    public void onLoadEntriesCompleted_extraInfoUpdated_rebuildsEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+
+        // Extra info updated callback happens synchronously onStart since we are using the main
+        // looper for testing.
+        callbacks.onLoadEntriesCompleted();
+
+        verify(mSession).rebuild(any(), eq(ApplicationsState.ALPHA_COMPARATOR), /* foreground= */
+                eq(false));
+    }
+
+    @Test
+    public void onRebuildComplete_addsPreferencesForEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+        ArrayList<AppEntry> entries = new ArrayList<>();
+        entries.add(createAppEntry("test.package", /* uid= */ 1, /* isOpPermissible= */ true));
+        entries.add(
+                createAppEntry("another.test.package", /* uid= */ 2, /* isOpPermissible= */ false));
+        callbacks.onLoadEntriesCompleted();
+
+        callbacks.onRebuildComplete(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);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+        ArrayList<AppEntry> entries = new ArrayList<>();
+        String packageName = "test.package";
+        int uid = 1;
+        entries.add(createAppEntry("test.package", uid, /* isOpPermissible= */ false));
+        callbacks.onLoadEntriesCompleted();
+        callbacks.onRebuildComplete(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);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+        ArrayList<AppEntry> entries = new ArrayList<>();
+        String packageName = "test.package";
+        int uid = 1;
+        entries.add(createAppEntry("test.package", uid, /* isOpPermissible= */ true));
+        callbacks.onLoadEntriesCompleted();
+        callbacks.onRebuildComplete(entries);
+        TwoStatePreference appPref = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+
+        appPref.performClick();
+
+        assertThat(getShadowAppOpsManager().getMode(APP_OP_CODE, uid, packageName)).isEqualTo(
+                NEGATIVE_MODE);
+    }
+
+    @Test
+    public void onPreferenceChange_rebuildsEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+        ArrayList<AppEntry> entries = new ArrayList<>();
+        String packageName = "test.package";
+        int uid = 1;
+        entries.add(createAppEntry("test.package", uid, /* isOpPermissible= */ false));
+        callbacks.onLoadEntriesCompleted();
+        callbacks.onRebuildComplete(entries);
+        TwoStatePreference appPref = (TwoStatePreference) mPreferenceGroup.getPreference(0);
+
+        appPref.performClick();
+
+        // 2 times: onLoadEntriesCompleted, onPreferenceChange
+        verify(mSession, times(2)).rebuild(any(),
+                eq(ApplicationsState.ALPHA_COMPARATOR), /* foreground= */
+                eq(false));
+    }
+
+    @Test
+    public void showSystem_rebuildsEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+        callbacks.onLoadEntriesCompleted();
+
+        mController.setShowSystem(true);
+
+        // 2 times: onLoadEntriesCompleted, setShowSystem
+        verify(mSession, times(2)).rebuild(any(),
+                eq(ApplicationsState.ALPHA_COMPARATOR), /* foreground= */
+                eq(false));
+    }
+
+    @Test
+    public void rebuildFilter_showingSystemApps_keepsSystemEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+        callbacks.onLoadEntriesCompleted();
+        mController.setShowSystem(true);
+        ArgumentCaptor<ApplicationsState.AppFilter> filterCaptor = ArgumentCaptor.forClass(
+                ApplicationsState.AppFilter.class);
+        // 2 times: onLoadEntriesCompleted, setShowSystem
+        verify(mSession, times(2)).rebuild(filterCaptor.capture(),
+                eq(ApplicationsState.ALPHA_COMPARATOR), /* foreground= */
+                eq(false));
+
+        // Get the filter from setShowSystem.
+        ApplicationsState.AppFilter filter = filterCaptor.getAllValues().get(1);
+
+        AppEntry systemApp = createAppEntry("test.package", /* uid= */ 1, /* isOpPermissible= */
+                false);
+        systemApp.info.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+        assertThat(filter.filterApp(systemApp)).isTrue();
+    }
+
+    @Test
+    public void rebuildFilter_notShowingSystemApps_removesSystemEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+        callbacks.onLoadEntriesCompleted();
+        ArgumentCaptor<ApplicationsState.AppFilter> filterCaptor = ArgumentCaptor.forClass(
+                ApplicationsState.AppFilter.class);
+        verify(mSession).rebuild(filterCaptor.capture(),
+                eq(ApplicationsState.ALPHA_COMPARATOR), /* foreground= */
+                eq(false));
+
+        // Not showing system by default
+        ApplicationsState.AppFilter filter = filterCaptor.getValue();
+
+        AppEntry systemApp = createAppEntry("test.package", /* uid= */ 1, /* isOpPermissible= */
+                false);
+        systemApp.info.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+        assertThat(filter.filterApp(systemApp)).isFalse();
+    }
+
+    @Test
+    public void rebuildFilter_removesNullExtraInfoEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ApplicationsState.Callbacks callbacks = mCallbackCaptor.getAllValues().get(0);
+        callbacks.onLoadEntriesCompleted();
+        ArgumentCaptor<ApplicationsState.AppFilter> filterCaptor = ArgumentCaptor.forClass(
+                ApplicationsState.AppFilter.class);
+        verify(mSession).rebuild(filterCaptor.capture(),
+                eq(ApplicationsState.ALPHA_COMPARATOR), /* foreground= */
+                eq(false));
+
+        ApplicationsState.AppFilter filter = filterCaptor.getValue();
+
+        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/common/SettingsFragmentTest.java b/tests/robotests/src/com/android/car/settings/common/SettingsFragmentTest.java
index cf11631..a19e3dd 100644
--- a/tests/robotests/src/com/android/car/settings/common/SettingsFragmentTest.java
+++ b/tests/robotests/src/com/android/car/settings/common/SettingsFragmentTest.java
@@ -37,6 +37,7 @@
 
 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;
@@ -272,7 +273,7 @@
     @Test
     public void onActivityCreated_hasAppIconIfRoot() {
         mFragmentController.setup();
-        TestSettingsFragment otherFragment = new TestSettingsFragment();
+        DummyFragment otherFragment = new DummyFragment();
         mFragment.launchFragment(otherFragment);
 
         FrameLayout actionBarContainer =
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/PasswordHelperTest.java b/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java
index bbc468a..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);
     }
 
     /**
@@ -51,7 +52,7 @@
     }
 
     /**
-     * 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() {
@@ -77,7 +78,7 @@
     @Test
     public void testValidatePinContainingNonDigits() {
         byte[] password = "1a34".getBytes();
-        assertThat(mPinPresenter.validate(password))
+        assertThat(mPinHelper.validate(password))
                 .isEqualTo(PasswordHelper.CONTAINS_NON_DIGITS);
     }
 
@@ -87,7 +88,71 @@
     @Test
     public void testValidatePinWithTooFewDigits() {
         byte[] password = "12".getBytes();
-        assertThat(mPinPresenter.validate(password))
+        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/testutils/BaseTestActivity.java b/tests/robotests/src/com/android/car/settings/testutils/BaseTestActivity.java
index ac770b9..5fff6f9 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/BaseTestActivity.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/BaseTestActivity.java
@@ -58,9 +58,10 @@
             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();
     }
diff --git a/tests/robotests/src/com/android/car/settings/testutils/DummyFragment.java b/tests/robotests/src/com/android/car/settings/testutils/DummyFragment.java
index ec93bee..dc6cfba 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/DummyFragment.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/DummyFragment.java
@@ -16,10 +16,15 @@
 
 package com.android.car.settings.testutils;
 
-import androidx.fragment.app.Fragment;
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
 
 /**
  * Empty Fragment.
  */
-public class DummyFragment extends 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/ShadowActivityThread.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityThread.java
index 5cd1ac6..2d51a86 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityThread.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowActivityThread.java
@@ -25,14 +25,29 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
 
 import java.lang.reflect.Proxy;
 
 @Implements(ActivityThread.class)
 public class ShadowActivityThread {
 
+    private static IPackageManager sIPackageManager;
+
+    public static void setPackageManager(IPackageManager packageManager) {
+        sIPackageManager = packageManager;
+    }
+
+    @Resetter
+    public static void reset() {
+        sIPackageManager = null;
+    }
+
     @Implementation
     protected static IPackageManager getPackageManager() {
+        if (sIPackageManager != null) {
+            return sIPackageManager;
+        }
         ClassLoader classLoader = ShadowActivityThread.class.getClassLoader();
         Class<?> iPackageManagerClass;
         try {
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowAppOpsManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowAppOpsManager.java
index b18c9ad..841af36 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowAppOpsManager.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowAppOpsManager.java
@@ -49,6 +49,12 @@
         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) {
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowCarWifiManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarWifiManager.java
index b5893cb..8215a8b 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowCarWifiManager.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarWifiManager.java
@@ -41,6 +41,8 @@
     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;
@@ -51,6 +53,8 @@
         sInstance = null;
         sWifiConfiguration = new WifiConfiguration();
         sCurrentState = STATE_UNKNOWN;
+        sIsDualModeSupported = true;
+        sIsDualBandSupported = true;
     }
 
     @Implementation
@@ -122,8 +126,26 @@
     }
 
     @Implementation
-    public void connectToSavedWifi(AccessPoint accessPoint, WifiManager.ActionListener listener) {
-        sInstance.connectToSavedWifi(accessPoint, listener);
+    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() {
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java
index 37fe981..8f819c3 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java
@@ -16,16 +16,28 @@
 
 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;
@@ -34,10 +46,51 @@
     @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/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/WifiTetherPasswordPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceControllerTest.java
index de99482..613525b 100644
--- a/tests/robotests/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/car/settings/wifi/WifiTetherPasswordPreferenceControllerTest.java
@@ -19,15 +19,20 @@
 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;
@@ -35,10 +40,9 @@
 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})
+@Config(shadows = {ShadowCarWifiManager.class, ShadowLocalBroadcastManager.class})
 public class WifiTetherPasswordPreferenceControllerTest {
 
     private static final String TEST_PASSWORD = "TEST_PASSWORD";
@@ -48,37 +52,62 @@
     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_wifiConfigHasPassword_setsSummary() {
+    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;
-        getShadowCarWifiManager().setWifiApConfig(config);
+        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_wifiConfigHasPassword_obscuresSummary() {
+    public void onStart_securityTypeIsNotNone_wifiConfigHasPassword_obscuresSummary() {
         WifiConfiguration config = new WifiConfiguration();
         config.preSharedKey = TEST_PASSWORD;
-        getShadowCarWifiManager().setWifiApConfig(config);
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        mCarWifiManager.setWifiApConfig(config);
         mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
 
         assertThat(mPreference.getSummaryInputType())
@@ -86,16 +115,137 @@
     }
 
     @Test
-    public void onStart_wifiConfigHasNoPassword_doesNotObscureSummary() {
+    public void onStart_securityTypeIsNotNone_wifiConfigHasNoPassword_passwordIsNotEmpty() {
         WifiConfiguration config = new WifiConfiguration();
-        config.preSharedKey = null;
-        getShadowCarWifiManager().setWifiApConfig(config);
+        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);
+        assertThat(!TextUtils.isEmpty(mPreference.getSummary())).isTrue();
     }
 
-    private ShadowCarWifiManager getShadowCarWifiManager() {
-        return Shadow.extract(mCarWifiManager);
+    @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());
+    }
+}