Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/res/layout/keyboard_layout_picker_one_pane_land.xml b/res/layout/keyboard_layout_picker_one_pane_land.xml
new file mode 100644
index 0000000..fb5554f
--- /dev/null
+++ b/res/layout/keyboard_layout_picker_one_pane_land.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyboard_layout_picker_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="@dimen/keyboard_picker_margin_one_pane"
+    android:layout_marginVertical="@dimen/keyboard_picker_margin_one_pane_large"
+    android:orientation="horizontal">
+
+    <FrameLayout
+        android:id="@+id/keyboard_layout_preview_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="@dimen/keyboard_picker_margin_one_pane"
+        android:background="@drawable/keyboard_review_layout_background">
+
+        <ImageView
+            android:id="@+id/keyboard_layout_preview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/keyboard_picker_margin_small"
+            android:layout_marginTop="@dimen/keyboard_picker_margin_small"
+            android:layout_marginBottom="@dimen/keyboard_picker_margin_large"
+            android:adjustViewBounds="true"
+            android:scaleType="fitCenter" />
+
+        <TextView
+            android:id="@+id/keyboard_layout_preview_name"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/keyboard_picker_margin_large"
+            android:layout_gravity="bottom"
+            android:gravity="center"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="@dimen/keyboard_picker_text_size" />
+    </FrameLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/keyboard_picker_margin_one_pane"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <FrameLayout
+            android:id="@+id/keyboard_layout_title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="?android:attr/colorBackground"
+            android:elevation="1dp"
+            android:outlineAmbientShadowColor="@android:color/transparent"
+            android:outlineSpotShadowColor="@android:color/transparent" />
+
+        <FrameLayout
+            android:id="@+id/keyboard_layouts"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/keyboard_picker_margin_small"
+            android:background="?android:attr/colorBackground" />
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index afd6fdd..9e91dcc 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -808,4 +808,8 @@
     <!-- Array of carrier id to allow the pSIM conversion-->
     <integer-array name="config_psim_conversion_menu_enabled_carrier" translatable="false">
     </integer-array>
+
+    <!-- Array of carrier id that uses reusable activation code-->
+    <integer-array name="config_carrier_use_rac" translatable="false">
+    </integer-array>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index bdf329c..cbfd3a8 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -168,6 +168,8 @@
     <!-- Keyboard -->
     <dimen name="keyboard_picker_margin_large">68dp</dimen>
     <dimen name="keyboard_picker_margin">24dp</dimen>
+    <dimen name="keyboard_picker_margin_one_pane_large">48dp</dimen>
+    <dimen name="keyboard_picker_margin_one_pane">24dp</dimen>
     <dimen name="keyboard_picker_margin_small">16dp</dimen>
     <dimen name="keyboard_picker_radius">28dp</dimen>
     <dimen name="keyboard_picker_text_size">16sp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cf13dfb..a369d3d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5164,8 +5164,8 @@
     <string name="accessibility_hearingaid_instruction_continue_button">Continue</string>
     <!-- Title for the accessibility preference for hearing devices. [CHAR LIMIT=35] -->
     <string name="accessibility_hearingaid_title">Hearing devices</string>
-    <!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE] -->
-    <string name="accessibility_hearingaid_intro">You can use hearing aids, cochlear implants, and other amplification devices with your phone</string>
+    <!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=5856992709195963850] -->
+    <string name="accessibility_hearingaid_intro">Set up and manage ASHA and LE Audio hearing aids, cochlear implants, and other amplification devices</string>
     <!-- Summary for the accessibility preference for hearing aid when not connected. [CHAR LIMIT=50] -->
     <string name="accessibility_hearingaid_not_connected_summary">No hearing devices connected</string>
     <!-- Summary for the accessibility preference for hearing aid when adding new devices. [CHAR LIMIT=50] -->
@@ -5200,6 +5200,8 @@
     <string name="accessibility_hac_mode_summary">Improves compatibility with telecoils and reduces unwanted noise</string>
     <!-- Title for accessibility hearing device page footer. [CHAR LIMIT=40] -->
     <string name="accessibility_hearing_device_about_title">About hearing devices</string>
+    <!-- Description for text in accessibility hearing aids footer. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=7451899224828040581] -->
+    <string name="accessibility_hearing_device_footer_summary">To find other hearing devices that aren’t supported by ASHA or LE Audio, tap <b>Pair new device</b> > <b>See more devices</b></string>
     <!-- Title for the pair hearing device page. [CHAR LIMIT=25] -->
     <string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
     <!-- Subtitle for the pair hearing device page. [CHAR LIMIT=NONE] -->
@@ -12342,6 +12344,15 @@
     <!-- Summary for UWB preference when UWB is unavailable due to regulatory requirements. [CHAR_LIMIT=NONE]-->
     <string name="uwb_settings_summary_no_uwb_regulatory">UWB is unavailable in the current location</string>
 
+    <!-- Title for Thread network preference [CHAR_LIMIT=60] -->
+    <string name="thread_network_settings_title">Thread</string>
+
+    <!-- Summary for Thread network preference. [CHAR_LIMIT=NONE]-->
+    <string name="thread_network_settings_summary">Connect to compatible devices using Thread for a seamless smart home experience</string>
+
+    <!-- Summary for Thread network preference when airplane mode is enabled. [CHAR_LIMIT=NONE]-->
+    <string name="thread_network_settings_summary_airplane_mode">Turn off airplane mode to use Thread</string>
+
     <!-- Label for the camera use toggle [CHAR LIMIT=40] -->
     <string name="camera_toggle_title">Camera access</string>
     <!-- Label for the camera use toggle [CHAR LIMIT=40] -->
diff --git a/res/xml/accessibility_hearing_aids.xml b/res/xml/accessibility_hearing_aids.xml
index 27a4212..57a0fe2 100644
--- a/res/xml/accessibility_hearing_aids.xml
+++ b/res/xml/accessibility_hearing_aids.xml
@@ -60,4 +60,11 @@
             settings:searchable="true"
             settings:controller="com.android.settings.accessibility.HearingAidCompatibilityPreferenceController"/>
     </PreferenceCategory>
+
+    <com.android.settings.accessibility.AccessibilityFooterPreference
+        android:key="hearing_device_footer"
+        android:title="@string/accessibility_hearing_device_footer_summary"
+        android:selectable="false"
+        settings:searchable="false"
+        settings:controller="com.android.settings.accessibility.HearingDeviceFooterPreferenceController"/>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml
index 66cd46b..87db619 100644
--- a/res/xml/connected_devices_advanced.xml
+++ b/res/xml/connected_devices_advanced.xml
@@ -63,6 +63,15 @@
         settings:useAdminDisabledSummary="true"
         settings:userRestriction="no_ultra_wideband_radio" />
 
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="thread_network_settings"
+        android:title="@string/thread_network_settings_title"
+        android:order="110"
+        android:summary="@string/summary_placeholder"
+        settings:controller="com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController"
+        settings:userRestriction="no_thread_network"
+        settings:useAdminDisabledSummary="true"/>
+
     <PreferenceCategory
         android:key="dashboard_tile_placeholder"
         android:order="-8" />
diff --git a/res/xml/hearing_device_pairing_fragment.xml b/res/xml/hearing_device_pairing_fragment.xml
index ce7f39f..d84f22b 100644
--- a/res/xml/hearing_device_pairing_fragment.xml
+++ b/res/xml/hearing_device_pairing_fragment.xml
@@ -38,5 +38,4 @@
             settings:useAdminDisabledSummary="true"
             settings:controller="com.android.settings.accessibility.ViewAllBluetoothDevicesPreferenceController"/>
     </PreferenceCategory>
-
 </PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 27a87f3..02205c1 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -38,6 +38,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.permission.flags.Flags;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -56,6 +57,7 @@
 import com.android.settings.Settings.WifiSettingsActivity;
 import com.android.settings.activityembedding.ActivityEmbeddingUtils;
 import com.android.settings.applications.manageapplications.ManageApplications;
+import com.android.settings.connecteddevice.NfcAndPaymentFragment;
 import com.android.settings.core.OnActivityResultListener;
 import com.android.settings.core.SettingsBaseActivity;
 import com.android.settings.core.SubSettingLauncher;
@@ -63,6 +65,7 @@
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.homepage.SettingsHomepageActivity;
 import com.android.settings.homepage.TopLevelSettings;
+import com.android.settings.nfc.PaymentSettings;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.password.PasswordUtils;
 import com.android.settings.wfd.WifiDisplaySettings;
@@ -828,12 +831,29 @@
             if (ai == null || ai.metaData == null) return;
             mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
             mHighlightMenuKey = ai.metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY);
+            /* TODO(b/327036144) Once the Flags.walletRoleEnabled() is rolled out, we will replace
+            value for the fragment class within the com.android.settings.nfc.PaymentSettings
+            activity with com.android.settings.connecteddevice.NfcAndPaymentFragment so that this
+            code can be removed.
+            */
+            if (shouldOverrideContactlessPaymentRouting()) {
+                overrideContactlessPaymentRouting();
+            }
         } catch (NameNotFoundException nnfe) {
             // No recovery
             Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
         }
     }
 
+    private boolean shouldOverrideContactlessPaymentRouting() {
+        return Flags.walletRoleEnabled()
+                && TextUtils.equals(PaymentSettings.class.getName(), mFragmentClass);
+    }
+
+    private void overrideContactlessPaymentRouting() {
+        mFragmentClass = NfcAndPaymentFragment.class.getName();
+    }
+
     // give subclasses access to the Next button
     public boolean hasNextButton() {
         return mNextButton != null;
diff --git a/src/com/android/settings/accessibility/ColorContrastFragment.java b/src/com/android/settings/accessibility/ColorContrastFragment.java
index a572657..904826e 100644
--- a/src/com/android/settings/accessibility/ColorContrastFragment.java
+++ b/src/com/android/settings/accessibility/ColorContrastFragment.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.accessibility;
 
+import android.app.settings.SettingsEnums;
+
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
@@ -40,8 +42,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO(b/326539398): Add metrics tracking for color contrast.
-        return 0;
+        return SettingsEnums.ACCESSIBILITY_COLOR_CONTRAST;
     }
 
     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
diff --git a/src/com/android/settings/accessibility/OWNERS b/src/com/android/settings/accessibility/OWNERS
index 1091a04..3bd156b 100644
--- a/src/com/android/settings/accessibility/OWNERS
+++ b/src/com/android/settings/accessibility/OWNERS
@@ -1,7 +1,12 @@
 # Default reviewers for this and subdirectories.
+chunkulin@google.com
 danielnorman@google.com
-menghanli@google.com
+
+# For hearing devices.
 thomasli@google.com
 
+# Legacy owner(s).
+menghanli@google.com #{LAST_RESORT_SUGGESTION}
+
 per-file HapticFeedbackIntensityPreferenceController.java = michaelwr@google.com
 per-file *Vibration* = michaelwr@google.com
diff --git a/src/com/android/settings/connecteddevice/threadnetwork/OWNERS b/src/com/android/settings/connecteddevice/threadnetwork/OWNERS
new file mode 100644
index 0000000..4a35359
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/threadnetwork/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/Connectivity:/thread/OWNERS
diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
new file mode 100644
index 0000000..10e3f84
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.connecteddevice.threadnetwork
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.net.thread.ThreadNetworkController
+import android.net.thread.ThreadNetworkController.StateCallback
+import android.net.thread.ThreadNetworkException
+import android.net.thread.ThreadNetworkManager
+import android.os.OutcomeReceiver
+import android.provider.Settings
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.net.thread.platform.flags.Flags
+import com.android.settings.R
+import com.android.settings.core.TogglePreferenceController
+import java.util.concurrent.Executor
+
+/** Controller for the "Thread" toggle in "Connected devices > Connection preferences".  */
+class ThreadNetworkPreferenceController @VisibleForTesting constructor(
+    context: Context,
+    key: String,
+    private val executor: Executor,
+    private val threadController: BaseThreadNetworkController?
+) : TogglePreferenceController(context, key), LifecycleEventObserver {
+    private val stateCallback: StateCallback
+    private val airplaneModeReceiver: BroadcastReceiver
+    private var threadEnabled = false
+    private var airplaneModeOn = false
+    private var preference: Preference? = null
+
+    /**
+     * A testable interface for [ThreadNetworkController] which is `final`.
+     *
+     * We are in a awkward situation that Android API guideline suggest `final` for API classes
+     * while Robolectric test is being deprecated for platform testing (See
+     * tests/robotests/new_tests_hook.sh). This force us to use "mockito-target-extended" but it's
+     * conflicting with the default "mockito-target" which is somehow indirectly depended by the
+     * `SettingsUnitTests` target.
+     */
+    @VisibleForTesting
+    interface BaseThreadNetworkController {
+        fun setEnabled(
+            enabled: Boolean,
+            executor: Executor,
+            receiver: OutcomeReceiver<Void?, ThreadNetworkException>
+        )
+
+        fun registerStateCallback(executor: Executor, callback: StateCallback)
+
+        fun unregisterStateCallback(callback: StateCallback)
+    }
+
+    constructor(context: Context, key: String) : this(
+        context,
+        key,
+        ContextCompat.getMainExecutor(context),
+        getThreadNetworkController(context)
+    )
+
+    init {
+        stateCallback = newStateCallback()
+        airplaneModeReceiver = newAirPlaneModeReceiver()
+    }
+
+    val isThreadSupportedOnDevice: Boolean
+        get() = threadController != null
+
+    private fun newStateCallback(): StateCallback {
+        return object : StateCallback {
+            override fun onThreadEnableStateChanged(enabledState: Int) {
+                threadEnabled = enabledState == ThreadNetworkController.STATE_ENABLED
+            }
+
+            override fun onDeviceRoleChanged(role: Int) {}
+        }
+    }
+
+    private fun newAirPlaneModeReceiver(): BroadcastReceiver {
+        return object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                airplaneModeOn = isAirplaneModeOn(context)
+                Log.i(TAG, "Airplane mode is " + if (airplaneModeOn) "ON" else "OFF")
+                preference?.let { preference -> updateState(preference) }
+            }
+        }
+    }
+
+    override fun getAvailabilityStatus(): Int {
+        return if (!Flags.threadEnabledPlatform()) {
+            CONDITIONALLY_UNAVAILABLE
+        } else if (!isThreadSupportedOnDevice) {
+            UNSUPPORTED_ON_DEVICE
+        } else if (airplaneModeOn) {
+            DISABLED_DEPENDENT_SETTING
+        } else {
+            AVAILABLE
+        }
+    }
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        super.displayPreference(screen)
+        preference = screen.findPreference(preferenceKey)
+    }
+
+    override fun isChecked(): Boolean {
+        // TODO (b/322742298):
+        // Check airplane mode here because it's planned to disable Thread state in airplane mode
+        // (code in the mainline module). But it's currently not implemented yet (b/322742298).
+        // By design, the toggle should be unchecked in airplane mode, so explicitly check the
+        // airplane mode here to acchieve the same UX.
+        return !airplaneModeOn && threadEnabled
+    }
+
+    override fun setChecked(isChecked: Boolean): Boolean {
+        if (threadController == null) {
+            return false
+        }
+        val action = if (isChecked) "enable" else "disable"
+        threadController.setEnabled(
+            isChecked,
+            executor,
+            object : OutcomeReceiver<Void?, ThreadNetworkException> {
+                override fun onError(e: ThreadNetworkException) {
+                    // TODO(b/327549838): gracefully handle the failure by resetting the UI state
+                    Log.e(TAG, "Failed to $action Thread", e)
+                }
+
+                override fun onResult(unused: Void?) {
+                    Log.d(TAG, "Successfully $action Thread")
+                }
+            })
+        return true
+    }
+
+    override fun onStateChanged(lifecycleOwner: LifecycleOwner, event: Lifecycle.Event) {
+        if (threadController == null) {
+            return
+        }
+
+        when (event) {
+            Lifecycle.Event.ON_START -> {
+                threadController.registerStateCallback(executor, stateCallback)
+                airplaneModeOn = isAirplaneModeOn(mContext)
+                mContext.registerReceiver(
+                    airplaneModeReceiver,
+                    IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+                )
+                preference?.let { preference -> updateState(preference) }
+            }
+            Lifecycle.Event.ON_STOP -> {
+                threadController.unregisterStateCallback(stateCallback)
+                mContext.unregisterReceiver(airplaneModeReceiver)
+            }
+            else -> {}
+        }
+    }
+
+    override fun updateState(preference: Preference) {
+        super.updateState(preference)
+        preference.isEnabled = !airplaneModeOn
+        refreshSummary(preference)
+    }
+
+    override fun getSummary(): CharSequence {
+        val resId: Int = if (airplaneModeOn) {
+            R.string.thread_network_settings_summary_airplane_mode
+        } else {
+            R.string.thread_network_settings_summary
+        }
+        return mContext.getResources().getString(resId)
+    }
+
+    override fun getSliceHighlightMenuRes(): Int {
+        return R.string.menu_key_connected_devices
+    }
+
+    companion object {
+        private const val TAG = "ThreadNetworkSettings"
+        private fun getThreadNetworkController(context: Context): BaseThreadNetworkController? {
+            if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_THREAD_NETWORK)) {
+                return null
+            }
+            val manager = context.getSystemService(ThreadNetworkManager::class.java) ?: return null
+            val controller = manager.allThreadNetworkControllers[0]
+            return object : BaseThreadNetworkController {
+                override fun setEnabled(
+                    enabled: Boolean,
+                    executor: Executor,
+                    receiver: OutcomeReceiver<Void?, ThreadNetworkException>
+                ) {
+                    controller.setEnabled(enabled, executor, receiver)
+                }
+
+                override fun registerStateCallback(executor: Executor, callback: StateCallback) {
+                    controller.registerStateCallback(executor, callback)
+                }
+
+                override fun unregisterStateCallback(callback: StateCallback) {
+                    controller.unregisterStateCallback(callback)
+                }
+            }
+        }
+
+        private fun isAirplaneModeOn(context: Context): Boolean {
+            return Settings.Global.getInt(
+                context.contentResolver,
+                Settings.Global.AIRPLANE_MODE_ON,
+                0
+            ) == 1
+        }
+    }
+}
diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerFragment.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerFragment.java
index 85ba5fb..674b5bf 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerFragment.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerFragment.java
@@ -16,9 +16,11 @@
 
 package com.android.settings.inputmethod;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
@@ -35,6 +37,7 @@
 
 import com.android.hardware.input.Flags;
 import com.android.settings.R;
+import com.android.settings.activityembedding.ActivityEmbeddingUtils;
 
 //TODO: b/316243168 - [Physical Keyboard Setting] Refactor NewKeyboardLayoutPickerFragment
 public class NewKeyboardLayoutPickerFragment extends Fragment {
@@ -81,7 +84,7 @@
             Bundle savedInstanceState) {
         mInputManager = requireContext().getSystemService(InputManager.class);
         ViewGroup fragmentView = (ViewGroup) inflater.inflate(
-                R.layout.keyboard_layout_picker, container, false);
+                getPickerLayout(getResources().getConfiguration()), container, false);
         mKeyboardLayoutPreview = fragmentView.findViewById(R.id.keyboard_layout_preview);
         mKeyboardLayoutPreviewText = fragmentView.findViewById(R.id.keyboard_layout_preview_name);
         if (!Flags.keyboardLayoutPreviewFlag()) {
@@ -102,6 +105,12 @@
         return fragmentView;
     }
 
+    private int getPickerLayout(Configuration configuration) {
+        return !ActivityEmbeddingUtils.isAlreadyEmbedded(this.getActivity())
+                && configuration.orientation == ORIENTATION_LANDSCAPE
+                ? R.layout.keyboard_layout_picker_one_pane_land : R.layout.keyboard_layout_picker;
+    }
+
     private void updateViewMarginForPreviewFlagOff(ViewGroup fragmentView) {
         LinearLayout previewContainer = fragmentView.findViewById(
                 R.id.keyboard_layout_picker_container);
diff --git a/src/com/android/settings/network/SimOnboardingActivity.kt b/src/com/android/settings/network/SimOnboardingActivity.kt
index 6347d7b..98bb5d7 100644
--- a/src/com/android/settings/network/SimOnboardingActivity.kt
+++ b/src/com/android/settings/network/SimOnboardingActivity.kt
@@ -45,6 +45,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -63,7 +64,10 @@
 import com.android.settingslib.spa.SpaBaseDialogActivity
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
 import com.android.settingslib.spa.widget.dialog.getDialogWidth
+import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
+import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
 import com.android.settingslib.spa.widget.ui.SettingsTitle
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.CoroutineScope
@@ -77,7 +81,7 @@
 class SimOnboardingActivity : SpaBaseDialogActivity() {
     lateinit var scope: CoroutineScope
     lateinit var showBottomSheet: MutableState<Boolean>
-    lateinit var showError: MutableState<Boolean>
+    lateinit var showError: MutableState<ErrorType>
     lateinit var showProgressDialog: MutableState<Boolean>
 
     private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null
@@ -102,7 +106,7 @@
 
         if (onboardingService.activeSubInfoList.isEmpty()) {
             // TODO: refactor and replace the ToggleSubscriptionDialogActivity
-            Log.e(TAG, "onboardingService.activeSubInfoList is empty" +
+            Log.d(TAG, "onboardingService.activeSubInfoList is empty" +
                     ", start ToggleSubscriptionDialogActivity")
             this.startActivity(ToggleSubscriptionDialogActivity
                     .getIntent(this.applicationContext, targetSubId, true))
@@ -113,10 +117,6 @@
         switchToEuiccSubscriptionSidecar = SwitchToEuiccSubscriptionSidecar.get(fragmentManager)
         switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager)
         enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager)
-
-        setContent {
-            Content()
-        }
     }
 
     override fun finish() {
@@ -125,15 +125,14 @@
         super.finish()
     }
 
-    var callbackListener: (Int) -> Unit = {
+    var callbackListener: (CallbackType) -> Unit = {
         Log.d(TAG, "Receive the CALLBACK: $it")
         when (it) {
-            CALLBACK_ERROR -> {
+            CallbackType.CALLBACK_ERROR -> {
                 setProgressDialog(false)
-                showError.value = true
             }
 
-            CALLBACK_ONBOARDING_COMPLETE -> {
+            CallbackType.CALLBACK_ONBOARDING_COMPLETE -> {
                 showBottomSheet.value = false
                 setProgressDialog(true)
                 scope.launch {
@@ -143,19 +142,19 @@
                 }
             }
 
-            CALLBACK_SETUP_NAME -> {
+            CallbackType.CALLBACK_SETUP_NAME -> {
                 scope.launch {
                     onboardingService.startSetupName()
                 }
             }
 
-            CALLBACK_SETUP_PRIMARY_SIM -> {
+            CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> {
                 scope.launch {
                     onboardingService.startSetupPrimarySim(this@SimOnboardingActivity)
                 }
             }
 
-            CALLBACK_FINISH -> {
+            CallbackType.CALLBACK_FINISH -> {
                 finish()
             }
         }
@@ -178,16 +177,14 @@
     @Composable
     override fun Content() {
         showBottomSheet = remember { mutableStateOf(false) }
-        showError = remember { mutableStateOf(false) }
+        showError = remember { mutableStateOf(ErrorType.ERROR_NONE) }
         showProgressDialog = remember { mutableStateOf(false) }
         scope = rememberCoroutineScope()
 
         registerSidecarReceiverFlow()
 
-        if(showError.value){
-            // show error
-            return
-        }
+        ErrorDialogImpl()
+
         LaunchedEffect(Unit) {
             if (onboardingService.activeSubInfoList.isNotEmpty()) {
                 showBottomSheet.value = true
@@ -255,6 +252,56 @@
     }
 
     @Composable
+    fun ErrorDialogImpl(){
+        // EuiccSlotSidecar showErrorDialog
+        val errorDialogPresenterForEuiccSlotSidecar = rememberAlertDialogPresenter(
+                confirmButton = AlertDialogButton(
+                        stringResource(android.R.string.ok)
+                ) {
+                    finish()
+                },
+                title = stringResource(R.string.privileged_action_disable_fail_title),
+                text = {
+                    Text(stringResource(R.string.privileged_action_disable_fail_text))
+                },
+        )
+
+        // RemovableSlotSidecar showErrorDialog
+        val errorDialogPresenterForRemovableSlotSidecar = rememberAlertDialogPresenter(
+                confirmButton = AlertDialogButton(
+                        stringResource(android.R.string.ok)
+                ) {
+                    finish()
+                },
+                title = stringResource(R.string.sim_action_enable_sim_fail_title),
+                text = {
+                    Text(stringResource(R.string.sim_action_enable_sim_fail_text))
+                },
+        )
+
+        // enableDSDS showErrorDialog
+        val errorDialogPresenterForMultiSimSidecar = rememberAlertDialogPresenter(
+                confirmButton = AlertDialogButton(
+                        stringResource(android.R.string.ok)
+                ) {
+                    finish()
+                },
+                title = stringResource(R.string.dsds_activation_failure_title),
+                text = {
+                    Text(stringResource(R.string.dsds_activation_failure_body_msg2))
+                },
+        )
+
+        // show error
+        when (showError.value) {
+            ErrorType.ERROR_EUICC_SLOT -> errorDialogPresenterForEuiccSlotSidecar.open()
+            ErrorType.ERROR_REMOVABLE_SLOT -> errorDialogPresenterForRemovableSlotSidecar.open()
+            ErrorType.ERROR_ENABLE_DSDS -> errorDialogPresenterForMultiSimSidecar.open()
+            else -> {}
+        }
+    }
+
+    @Composable
     fun registerSidecarReceiverFlow(){
         switchToEuiccSubscriptionSidecar?.sidecarReceiverFlow()
             ?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
@@ -321,13 +368,14 @@
             SidecarFragment.State.SUCCESS -> {
                 Log.i(TAG, "Successfully enable the eSIM profile.")
                 switchToEuiccSubscriptionSidecar!!.reset()
-                callbackListener(CALLBACK_SETUP_NAME)
+                callbackListener(CallbackType.CALLBACK_SETUP_NAME)
             }
 
             SidecarFragment.State.ERROR -> {
                 Log.i(TAG, "Failed to enable the eSIM profile.")
                 switchToEuiccSubscriptionSidecar!!.reset()
-                callbackListener(CALLBACK_ERROR)
+                showError.value = ErrorType.ERROR_EUICC_SLOT
+                callbackListener(CallbackType.CALLBACK_ERROR)
                 // TODO: showErrorDialog and using privileged_action_disable_fail_title and
                 //       privileged_action_disable_fail_text
             }
@@ -340,13 +388,14 @@
                 Log.i(TAG, "Successfully switched to removable slot.")
                 switchToRemovableSlotSidecar!!.reset()
                 onboardingService.handleTogglePsimAction()
-                callbackListener(CALLBACK_SETUP_NAME)
+                callbackListener(CallbackType.CALLBACK_SETUP_NAME)
             }
 
             SidecarFragment.State.ERROR -> {
                 Log.e(TAG, "Failed switching to removable slot.")
                 switchToRemovableSlotSidecar!!.reset()
-                callbackListener(CALLBACK_ERROR)
+                showError.value = ErrorType.ERROR_REMOVABLE_SLOT
+                callbackListener(CallbackType.CALLBACK_ERROR)
                 // TODO: showErrorDialog and using sim_action_enable_sim_fail_title and
                 //       sim_action_enable_sim_fail_text
             }
@@ -364,7 +413,8 @@
             SidecarFragment.State.ERROR -> {
                 enableMultiSimSidecar!!.reset()
                 Log.i(TAG, "Failed to switch to DSDS without rebooting.")
-                callbackListener(CALLBACK_ERROR)
+                showError.value = ErrorType.ERROR_ENABLE_DSDS
+                callbackListener(CallbackType.CALLBACK_ERROR)
                 // TODO: showErrorDialog and using dsds_activation_failure_title and
                 //       dsds_activation_failure_body_msg2
             }
@@ -387,7 +437,7 @@
         }
         Log.i(TAG, "DSDS enabled, start to enable pSIM profile.")
         onboardingService.handleTogglePsimAction()
-        callbackListener(CALLBACK_FINISH)
+        callbackListener(CallbackType.CALLBACK_FINISH)
     }
 
     @Composable
@@ -443,7 +493,7 @@
         Log.i(TAG, "setProgressState:$state")
     }
 
-    fun initServiceData(context: Context,targetSubId: Int, callback:(Int)->Unit) {
+    fun initServiceData(context: Context,targetSubId: Int, callback:(CallbackType)->Unit) {
         onboardingService.initData(targetSubId, context,callback)
     }
 
@@ -462,10 +512,20 @@
         var onboardingService:SimOnboardingService = SimOnboardingService()
         const val TAG = "SimOnboardingActivity"
         const val SUB_ID = "sub_id"
-        const val CALLBACK_ERROR = -1
-        const val CALLBACK_ONBOARDING_COMPLETE = 1
-        const val CALLBACK_SETUP_NAME = 2
-        const val CALLBACK_SETUP_PRIMARY_SIM = 3
-        const val CALLBACK_FINISH = 4
+
+        enum class ErrorType(val value:Int){
+            ERROR_NONE(-1),
+            ERROR_EUICC_SLOT(1),
+            ERROR_REMOVABLE_SLOT(2),
+            ERROR_ENABLE_DSDS(3)
+        }
+
+        enum class CallbackType(val value:Int){
+            CALLBACK_ERROR(-1),
+            CALLBACK_ONBOARDING_COMPLETE(1),
+            CALLBACK_SETUP_NAME(2),
+            CALLBACK_SETUP_PRIMARY_SIM(3),
+            CALLBACK_FINISH(4)
+        }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt
index 5387ad4..962741f 100644
--- a/src/com/android/settings/network/SimOnboardingService.kt
+++ b/src/com/android/settings/network/SimOnboardingService.kt
@@ -23,6 +23,7 @@
 import android.telephony.UiccCardInfo
 import android.telephony.UiccSlotInfo
 import android.util.Log
+import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
 import com.android.settings.spa.network.setAutomaticData
 import com.android.settings.spa.network.setDefaultData
 import com.android.settings.spa.network.setDefaultSms
@@ -31,7 +32,6 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 
-
 private const val TAG = "SimOnboardingService"
 private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
 
@@ -60,7 +60,7 @@
                 .map { it.subscriptionId }
                 .firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
         }
-    var callback: (Int) -> Unit = {}
+    var callback: (CallbackType) -> Unit = {}
 
     var isMultipleEnabledProfilesSupported: Boolean = false
         get() {
@@ -135,7 +135,9 @@
         userSelectedSubInfoList.clear()
     }
 
-    fun initData(inputTargetSubId:Int,context: Context, callback: (Int) -> Unit) {
+    fun initData(inputTargetSubId: Int,
+                 context: Context,
+                 callback: (CallbackType) -> Unit) {
         this.callback = callback
         targetSubId = inputTargetSubId
         subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
@@ -261,7 +263,7 @@
 
     fun startActivatingSim(){
         // TODO: start to activate sim
-        callback(SimOnboardingActivity.CALLBACK_FINISH)
+        callback(CallbackType.CALLBACK_FINISH)
     }
 
     suspend fun startSetupName() {
@@ -273,7 +275,7 @@
                 )
             }
             // next action is SETUP_PRIMARY_SIM
-            callback(SimOnboardingActivity.CALLBACK_SETUP_PRIMARY_SIM)
+            callback(CallbackType.CALLBACK_SETUP_PRIMARY_SIM)
         }
     }
 
@@ -302,7 +304,7 @@
             }
 
             // no next action, send finish
-            callback(SimOnboardingActivity.CALLBACK_FINISH)
+            callback(CallbackType.CALLBACK_FINISH)
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index 2498ec9..83f6c38 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -49,9 +49,11 @@
 import com.android.settings.network.helper.SelectableSubscriptions;
 import com.android.settings.network.helper.SubscriptionAnnotation;
 import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
+import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
 import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -557,13 +559,21 @@
      * @param context {@code Context}
      * @param subId The id of subscription need to be deleted.
      */
-    public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId) {
+    public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId,
+            int carrierId) {
         if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
             Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
             return;
         }
-        // TODO(b/325693582): Add verification if carrier is RAC and logic for new dialog
-        context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
+        final int[] carriersThatUseRAC = context.getResources().getIntArray(
+                R.array.config_carrier_use_rac);
+        boolean isCarrierRac = Arrays.stream(carriersThatUseRAC).anyMatch(cid -> cid == carrierId);
+
+        if (isCarrierRac && !isConnectedToWifiOrDifferentSubId(context, subId)) {
+            context.startActivity(EuiccRacConnectivityDialogActivity.getIntent(context, subId));
+        } else {
+            context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
+        }
     }
 
     /**
diff --git a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
index 64f9730..b7ee41d 100644
--- a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
@@ -33,6 +33,7 @@
 class DeleteSimProfilePreferenceController(context: Context, preferenceKey: String) :
     BasePreferenceController(context, preferenceKey) {
     private var subscriptionId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+    private var carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID
     private var subscriptionInfo: SubscriptionInfo? = null
     private lateinit var preference: Preference
 
@@ -40,6 +41,9 @@
         this.subscriptionId = subscriptionId
         subscriptionInfo = SubscriptionUtil.getAvailableSubscriptions(mContext)
             .find { it.subscriptionId == subscriptionId && it.isEmbedded }
+        subscriptionInfo?.let {
+            carrierId = it.carrierId
+        }
     }
 
     override fun getAvailabilityStatus() = when (subscriptionInfo) {
@@ -67,7 +71,8 @@
     }
 
     private fun deleteSim() {
-        SubscriptionUtil.startDeleteEuiccSubscriptionDialogActivity(mContext, subscriptionId)
+        SubscriptionUtil.startDeleteEuiccSubscriptionDialogActivity(mContext, subscriptionId,
+                carrierId)
         // result handled in MobileNetworkSettings
     }
 }
diff --git a/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java b/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
index 94940b3..5e8a3c3 100644
--- a/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
+++ b/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
@@ -29,6 +29,7 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.network.CarrierConfigCache;
@@ -58,6 +59,11 @@
 
     @Override
     public int getAvailabilityStatus(int subId) {
+        if (!Flags.carrierEnabledSatelliteFlag()) {
+            logd("getAvailabilityStatus() : carrierEnabledSatelliteFlag is disabled");
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
         final PersistableBundle carrierConfig = mCarrierConfigCache.getConfigForSubId(subId);
         final boolean isSatelliteAttachSupported = carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL);
diff --git a/src/com/android/settings/notification/MediaVolumePreferenceController.java b/src/com/android/settings/notification/MediaVolumePreferenceController.java
index 79df55a..e70cf95 100644
--- a/src/com/android/settings/notification/MediaVolumePreferenceController.java
+++ b/src/com/android/settings/notification/MediaVolumePreferenceController.java
@@ -37,6 +37,7 @@
 import com.android.settings.slices.CustomSliceRegistry;
 import com.android.settings.slices.SliceBackgroundWorker;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
 import com.android.settingslib.media.BluetoothMediaDevice;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.media.MediaOutputConstants;
@@ -94,7 +95,9 @@
 
     @VisibleForTesting
     boolean isSupportEndItem() {
-        return getWorker() != null && getWorker().isBroadcastSupported()
+        return Flags.legacyLeAudioSharing()
+                && getWorker() != null
+                && getWorker().isBroadcastSupported()
                 && (getWorker().isDeviceBroadcasting() || isConnectedBLEDevice());
     }
 
@@ -114,8 +117,9 @@
         if (mPreference != null) {
             if (mPreference.isMuted()) {
                 mPreference.updateContentDescription(
-                        mContext.getString(R.string.volume_content_description_silent_mode,
-                        mPreference.getTitle()));
+                        mContext.getString(
+                                R.string.volume_content_description_silent_mode,
+                                mPreference.getTitle()));
             } else {
                 mPreference.updateContentDescription(mPreference.getTitle());
             }
@@ -134,11 +138,16 @@
         if (getWorker().isDeviceBroadcasting()) {
             intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
             intent.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
-            intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
+            intent.putExtra(
+                    MediaOutputConstants.EXTRA_PACKAGE_NAME,
                     getWorker().getActiveLocalMediaController().getPackageName());
 
-            pi = PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
-                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            pi =
+                    PendingIntent.getBroadcast(
+                            context,
+                            0 /* requestCode */,
+                            intent,
+                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         } else {
             final CachedBluetoothDevice bluetoothDevice =
                     ((BluetoothMediaDevice) mMediaDevice).getCachedDevice();
@@ -147,15 +156,21 @@
                 return null;
             }
             intent.setAction(ACTION_LAUNCH_BROADCAST_DIALOG);
-            intent.putExtra(BluetoothBroadcastDialog.KEY_APP_LABEL,
+            intent.putExtra(
+                    BluetoothBroadcastDialog.KEY_APP_LABEL,
                     Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
-            intent.putExtra(BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS,
-                    bluetoothDevice.getAddress());
-            intent.putExtra(BluetoothBroadcastDialog.KEY_MEDIA_STREAMING, getWorker() != null
-                    && getWorker().getActiveLocalMediaController() != null);
+            intent.putExtra(
+                    BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS, bluetoothDevice.getAddress());
+            intent.putExtra(
+                    BluetoothBroadcastDialog.KEY_MEDIA_STREAMING,
+                    getWorker() != null && getWorker().getActiveLocalMediaController() != null);
 
-            pi = PendingIntent.getActivity(context, 0 /* requestCode */, intent,
-                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            pi =
+                    PendingIntent.getActivity(
+                            context,
+                            0 /* requestCode */,
+                            intent,
+                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         }
 
         final IconCompat icon = getBroadcastIcon(context);
@@ -164,8 +179,8 @@
     }
 
     private IconCompat getBroadcastIcon(Context context) {
-        final Drawable drawable = context.getDrawable(
-                com.android.settingslib.R.drawable.settings_input_antenna);
+        final Drawable drawable =
+                context.getDrawable(com.android.settingslib.R.drawable.settings_input_antenna);
         if (drawable != null) {
             drawable.setTint(Utils.getColorAccentDefaultColor(context));
             return Utils.createIconWithDrawable(drawable);
diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java
index 4544e73..8840994 100644
--- a/src/com/android/settings/sim/SimDialogActivity.java
+++ b/src/com/android/settings/sim/SimDialogActivity.java
@@ -136,9 +136,7 @@
         }
 
         if (Flags.isDualSimOnboardingEnabled()
-                && getProgressState() == SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
-                && (dialogType == PREFERRED_PICK
-                || dialogType == DATA_PICK
+                && (dialogType == DATA_PICK
                 || dialogType == CALLS_PICK
                 || dialogType == SMS_PICK)) {
             Log.d(TAG, "Finish the sim dialog since the sim onboarding is shown");
diff --git a/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt b/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
index cc17f93..838154f 100644
--- a/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
+++ b/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
@@ -34,6 +34,7 @@
 import androidx.navigation.navArgument
 import com.android.settings.R
 import com.android.settings.network.SimOnboardingActivity
+import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
 import com.android.settings.network.SimOnboardingService
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -93,7 +94,7 @@
     val context = LocalContext.current
     var finishOnboarding: () -> Unit = {
         context.getActivity()?.finish()
-        onboardingService.callback(SimOnboardingActivity.CALLBACK_FINISH)
+        onboardingService.callback(CallbackType.CALLBACK_FINISH)
     }
 
     NavHost(
@@ -120,7 +121,7 @@
         composable(route = SimOnboardingScreen.PrimarySim.name) {
             SimOnboardingPrimarySimImpl(
                 nextAction = {
-                    onboardingService.callback(SimOnboardingActivity.CALLBACK_ONBOARDING_COMPLETE)
+                    onboardingService.callback(CallbackType.CALLBACK_ONBOARDING_COMPLETE)
                     context.getActivity()?.finish()
                 },
                 cancelAction = finishOnboarding,
diff --git a/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java
index f903745..3077637 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.UiModeManager;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -59,7 +60,8 @@
 
     @Test
     public void getMetricsCategory_returnsCorrectCategory() {
-        assertThat(mFragment.getMetricsCategory()).isEqualTo(0);
+        assertThat(mFragment.getMetricsCategory()).isEqualTo(
+                SettingsEnums.ACCESSIBILITY_COLOR_CONTRAST);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
index ed93473..a25f472 100644
--- a/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
@@ -31,17 +31,23 @@
 import android.media.AudioManager;
 import android.media.session.MediaController;
 import android.net.Uri;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.slice.builders.SliceAction;
 
 import com.android.settings.media.MediaOutputIndicatorWorker;
 import com.android.settings.slices.SliceBackgroundWorker;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
 import com.android.settingslib.media.BluetoothMediaDevice;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.media.MediaOutputConstants;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -60,16 +66,16 @@
             "android.settings.MEDIA_BROADCAST_DIALOG";
     private static MediaOutputIndicatorWorker sMediaOutputIndicatorWorker;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private MediaVolumePreferenceController mController;
 
     private Context mContext;
 
-    @Mock
-    private MediaController mMediaController;
-    @Mock
-    private MediaDevice mDevice1;
-    @Mock
-    private MediaDevice mDevice2;
+    @Mock private MediaController mMediaController;
+    @Mock private MediaDevice mDevice1;
+    @Mock private MediaDevice mDevice2;
 
     @Before
     public void setUp() {
@@ -77,8 +83,8 @@
 
         mContext = RuntimeEnvironment.application;
         mController = new MediaVolumePreferenceController(mContext);
-        sMediaOutputIndicatorWorker = spy(
-                new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
+        sMediaOutputIndicatorWorker =
+                spy(new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
         when(mDevice1.isBLEDevice()).thenReturn(true);
         when(mDevice2.isBLEDevice()).thenReturn(false);
     }
@@ -101,8 +107,8 @@
 
     @Test
     public void isSliceableCorrectKey_returnsTrue() {
-        final MediaVolumePreferenceController controller = new MediaVolumePreferenceController(
-                mContext);
+        final MediaVolumePreferenceController controller =
+                new MediaVolumePreferenceController(mContext);
         assertThat(controller.isSliceable()).isTrue();
     }
 
@@ -112,6 +118,17 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+    public void isSupportEndItem_flagOff_returnsFalse() {
+        doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
+        doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
+        doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
+
+        assertThat(mController.isSupportEndItem()).isFalse();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void isSupportEndItem_withBleDevice_returnsTrue() {
         doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
         doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -121,6 +138,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void isSupportEndItem_notSupportedBroadcast_returnsFalse() {
         doReturn(false).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
         doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
@@ -129,6 +147,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void isSupportEndItem_withNonBleDevice_returnsFalse() {
         doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
         doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -138,6 +157,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void isSupportEndItem_deviceIsBroadcastingAndConnectedToNonBleDevice_returnsTrue() {
         doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
         doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -147,6 +167,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void isSupportEndItem_deviceIsNotBroadcastingAndConnectedToNonBleDevice_returnsFalse() {
         doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
         doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -155,8 +176,20 @@
         assertThat(mController.isSupportEndItem()).isFalse();
     }
 
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+    public void getSliceEndItem_flagOff_getsNullSliceAction() {
+        doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
+        doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
+        doReturn(mDevice2).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
+
+        final SliceAction sliceAction = mController.getSliceEndItem(mContext);
+
+        assertThat(sliceAction).isNull();
+    }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void getSliceEndItem_NotSupportEndItem_getsNullSliceAction() {
         doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
         doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -168,22 +201,26 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void getSliceEndItem_deviceIsBroadcasting_getsBroadcastIntent() {
         doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
         doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
         doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
-        doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
+        doReturn(mMediaController)
+                .when(sMediaOutputIndicatorWorker)
                 .getActiveLocalMediaController();
 
         final SliceAction sliceAction = mController.getSliceEndItem(mContext);
 
         final PendingIntent endItemPendingIntent = sliceAction.getAction();
-        final PendingIntent expectedToggleIntent = getBroadcastIntent(
-                MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+        final PendingIntent expectedToggleIntent =
+                getBroadcastIntent(
+                        MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         assertThat(endItemPendingIntent).isEqualTo(expectedToggleIntent);
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void getSliceEndItem_deviceIsNotBroadcasting_getsActivityIntent() {
         final MediaDevice device = mock(BluetoothMediaDevice.class);
         final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
@@ -192,7 +229,8 @@
         doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
         doReturn(device).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
         doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
-        doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
+        doReturn(mMediaController)
+                .when(sMediaOutputIndicatorWorker)
                 .getActiveLocalMediaController();
 
         final SliceAction sliceAction = mController.getSliceEndItem(mContext);
@@ -215,13 +253,19 @@
     private PendingIntent getBroadcastIntent(String action) {
         final Intent intent = new Intent(action);
         intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
-        return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
+        return PendingIntent.getBroadcast(
+                mContext,
+                0 /* requestCode */,
+                intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     private PendingIntent getActivityIntent(String action) {
         final Intent intent = new Intent(action);
-        return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent,
+        return PendingIntent.getActivity(
+                mContext,
+                0 /* requestCode */,
+                intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java b/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
index d696342..4b05b91 100644
--- a/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
+++ b/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
@@ -34,7 +34,6 @@
 import com.android.settings.testutils.shadow.ShadowUserManager;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
@@ -44,7 +43,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@Ignore("b/315133235")
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowUserManager.class, ShadowDevicePolicyManager.class})
 public class RestrictedButtonTest {
diff --git a/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
index 35dc666..530517f 100644
--- a/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
@@ -35,18 +35,19 @@
 
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.R;
 import com.android.settings.testutils.shadow.ShadowSettingsMediaPlayer;
 
 import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.androidx.fragment.FragmentController;
 
@@ -55,7 +56,8 @@
 public class VideoPreferenceTest {
     private static final int VIDEO_WIDTH = 100;
     private static final int VIDEO_HEIGHT = 150;
-
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
     private VideoPreference.AnimationController mAnimationController;
     @Mock
     private ImageView fakePreview;
@@ -68,9 +70,7 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mContext = RuntimeEnvironment.application;
+        mContext = ApplicationProvider.getApplicationContext();
         mAnimationController = spy(
                 new MediaAnimationController(mContext, R.raw.sample_video));
         mVideoPreference = new VideoPreference(mContext, null /* attrs */);
@@ -141,7 +141,6 @@
         assertThat(mAnimationController.isPlaying()).isTrue();
     }
 
-    @Ignore("b/315133235")
     @Test
     @Config(qualifiers = "mcc999")
     public void onViewVisible_createAnimationController() {
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 0f844d0..c8e886c 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -26,6 +26,7 @@
         "androidx.test.rules",
         "androidx.test.ext.junit",
         "androidx.preference_preference",
+        "flag-junit",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
         "platform-test-rules",
diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS
new file mode 100644
index 0000000..4a35359
--- /dev/null
+++ b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/Connectivity:/thread/OWNERS
diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
new file mode 100644
index 0000000..644095d
--- /dev/null
+++ b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.connecteddevice.threadnetwork
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.net.thread.ThreadNetworkController.STATE_DISABLED
+import android.net.thread.ThreadNetworkController.STATE_DISABLING
+import android.net.thread.ThreadNetworkController.STATE_ENABLED
+import android.net.thread.ThreadNetworkController.StateCallback
+import android.net.thread.ThreadNetworkException
+import android.os.OutcomeReceiver
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.PreferenceManager
+import androidx.preference.SwitchPreference
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.net.thread.platform.flags.Flags
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController.AVAILABLE
+import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE
+import com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING
+import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE
+import com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController.BaseThreadNetworkController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import java.util.concurrent.Executor
+
+/** Unit tests for [ThreadNetworkPreferenceController].  */
+@RunWith(AndroidJUnit4::class)
+class ThreadNetworkPreferenceControllerTest {
+    @get:Rule
+    val mSetFlagsRule = SetFlagsRule()
+    private lateinit var context: Context
+    private lateinit var executor: Executor
+    private lateinit var controller: ThreadNetworkPreferenceController
+    private lateinit var fakeThreadNetworkController: FakeThreadNetworkController
+    private lateinit var preference: SwitchPreference
+    private val broadcastReceiverArgumentCaptor = ArgumentCaptor.forClass(
+        BroadcastReceiver::class.java
+    )
+
+    @Before
+    fun setUp() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_ENABLED_PLATFORM)
+        context = spy(ApplicationProvider.getApplicationContext<Context>())
+        executor = ContextCompat.getMainExecutor(context)
+        fakeThreadNetworkController = FakeThreadNetworkController(executor)
+        controller = newControllerWithThreadFeatureSupported(true)
+        val preferenceManager = PreferenceManager(context)
+        val preferenceScreen = preferenceManager.createPreferenceScreen(context)
+        preference = SwitchPreference(context)
+        preference.key = "thread_network_settings"
+        preferenceScreen.addPreference(preference)
+        controller.displayPreference(preferenceScreen)
+
+        Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
+    }
+
+    private fun newControllerWithThreadFeatureSupported(
+        present: Boolean
+    ): ThreadNetworkPreferenceController {
+        return ThreadNetworkPreferenceController(
+            context,
+            "thread_network_settings" /* key */,
+            executor,
+            if (present) fakeThreadNetworkController else null
+        )
+    }
+
+    @Test
+    fun availabilityStatus_flagDisabled_returnsConditionallyUnavailable() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_THREAD_ENABLED_PLATFORM)
+        assertThat(controller.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE)
+    }
+
+    @Test
+    fun availabilityStatus_airPlaneModeOn_returnsDisabledDependentSetting() {
+        Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        assertThat(controller.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING)
+    }
+
+    @Test
+    fun availabilityStatus_airPlaneModeOff_returnsAvailable() {
+        Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE)
+    }
+
+    @Test
+    fun availabilityStatus_threadFeatureNotSupported_returnsUnsupported() {
+        controller = newControllerWithThreadFeatureSupported(false)
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        assertThat(fakeThreadNetworkController.registeredStateCallback).isNull()
+        assertThat(controller.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE)
+    }
+
+    @Test
+    fun isChecked_threadSetEnabled_returnsTrue() {
+        fakeThreadNetworkController.setEnabled(true, executor) { }
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        assertThat(controller.isChecked).isTrue()
+    }
+
+    @Test
+    fun isChecked_threadSetDisabled_returnsFalse() {
+        fakeThreadNetworkController.setEnabled(false, executor) { }
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        assertThat(controller.isChecked).isFalse()
+    }
+
+    @Test
+    fun setChecked_setChecked_threadIsEnabled() {
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        controller.setChecked(true)
+
+        assertThat(fakeThreadNetworkController.isEnabled).isTrue()
+    }
+
+    @Test
+    fun setChecked_setUnchecked_threadIsDisabled() {
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        controller.setChecked(false)
+
+        assertThat(fakeThreadNetworkController.isEnabled).isFalse()
+    }
+
+    @Test
+    fun updatePreference_airPlaneModeOff_preferenceEnabled() {
+        Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        assertThat(preference.isEnabled).isTrue()
+        assertThat(preference.summary).isEqualTo(
+            context.resources.getString(R.string.thread_network_settings_summary)
+        )
+    }
+
+    @Test
+    fun updatePreference_airPlaneModeOn_preferenceDisabled() {
+        Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+        assertThat(preference.isEnabled).isFalse()
+        assertThat(preference.summary).isEqualTo(
+            context.resources.getString(R.string.thread_network_settings_summary_airplane_mode)
+        )
+    }
+
+    @Test
+    fun updatePreference_airPlaneModeTurnedOn_preferenceDisabled() {
+        Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
+        startControllerAndCaptureCallbacks()
+
+        Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
+        broadcastReceiverArgumentCaptor.value.onReceive(context, Intent())
+
+        assertThat(preference.isEnabled).isFalse()
+        assertThat(preference.summary).isEqualTo(
+            context.resources.getString(R.string.thread_network_settings_summary_airplane_mode)
+        )
+    }
+
+    private fun startControllerAndCaptureCallbacks() {
+        controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+        verify(context)!!.registerReceiver(broadcastReceiverArgumentCaptor.capture(), any())
+    }
+
+    private class FakeThreadNetworkController(private val executor: Executor) :
+        BaseThreadNetworkController {
+        var isEnabled = true
+            private set
+        var registeredStateCallback: StateCallback? = null
+            private set
+
+        override fun setEnabled(
+            enabled: Boolean,
+            executor: Executor,
+            receiver: OutcomeReceiver<Void?, ThreadNetworkException>
+        ) {
+            isEnabled = enabled
+            if (registeredStateCallback != null) {
+                if (!isEnabled) {
+                    executor.execute {
+                        registeredStateCallback!!.onThreadEnableStateChanged(
+                            STATE_DISABLING
+                        )
+                    }
+                    executor.execute {
+                        registeredStateCallback!!.onThreadEnableStateChanged(
+                            STATE_DISABLED
+                        )
+                    }
+                } else {
+                    executor.execute {
+                        registeredStateCallback!!.onThreadEnableStateChanged(
+                            STATE_ENABLED
+                        )
+                    }
+                }
+            }
+            executor.execute { receiver.onResult(null) }
+        }
+
+        override fun registerStateCallback(
+            executor: Executor,
+            callback: StateCallback
+        ) {
+            require(callback !== registeredStateCallback) { "callback is already registered" }
+            registeredStateCallback = callback
+            val enabledState =
+                if (isEnabled) STATE_ENABLED else STATE_DISABLED
+            executor.execute { registeredStateCallback!!.onThreadEnableStateChanged(enabledState) }
+        }
+
+        override fun unregisterStateCallback(callback: StateCallback) {
+            requireNotNull(registeredStateCallback) { "callback is already unregistered" }
+            registeredStateCallback = null
+        }
+    }
+}