Merge "Fix tests in UserSettingsTest" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9723ed4..095452c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4285,6 +4285,15 @@
                        android:value="true" />
         </activity>
 
+        <!-- Access to the Credential Manager list. -->
+        <activity android:name=".applications.credentials.CredentialsPickerActivity"
+                android:excludeFromRecents="true"
+                android:launchMode="singleInstance"
+                android:exported="false">
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+                       android:value="com.android.settings.applications.credentials.DefaultCombinedPicker" />
+        </activity>
+
         <activity
             android:name=".Settings$SystemDashboardActivity"
             android:label="@string/header_category_system"
diff --git a/res/drawable/battery_hints_chip_bg.xml b/res/drawable/battery_hints_chip_bg.xml
index e7d1d0f..c2b662c 100644
--- a/res/drawable/battery_hints_chip_bg.xml
+++ b/res/drawable/battery_hints_chip_bg.xml
@@ -16,7 +16,8 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
-    <solid android:color="@color/settingslib_dialog_background" />
+    <solid android:color="?androidprv:attr/materialColorPrimaryContainer" />
     <corners android:radius="@dimen/battery_hints_chip_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/res/layout/power_anomaly_hints.xml b/res/layout/power_anomaly_hints.xml
index e4ec47b..3781046 100644
--- a/res/layout/power_anomaly_hints.xml
+++ b/res/layout/power_anomaly_hints.xml
@@ -17,6 +17,7 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
@@ -36,7 +37,7 @@
         android:layout_height="wrap_content"
         android:paddingHorizontal="8dp"
         android:textAlignment="viewStart"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?android:attr/textColorPrimary"/>
+        android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
+        android:textColor="?androidprv:attr/materialColorOnSurface"/>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index 8e9d45d..76ef984 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -65,6 +65,7 @@
         android:icon="@drawable/ic_wifi_tethering"
         android:order="5"
         android:summary="@string/summary_placeholder"
+        settings:controller="com.android.settings.network.TetherPreferenceController"
         settings:keywords="@string/keywords_hotspot_tethering"
         settings:userRestriction="no_config_tethering"
         settings:useAdminDisabledSummary="true" />
diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
index 074fb7b..e8541389 100644
--- a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
+++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
@@ -82,6 +82,37 @@
         return mAutofillServiceInfo.getServiceInfo().applicationInfo;
     }
 
+    /** Returns the package name. */
+    public @Nullable String getPackageName() {
+        ApplicationInfo ai = getApplicationInfo();
+        if (ai != null) {
+            return ai.packageName;
+        }
+
+        return null;
+    }
+
+    /** Returns the settings activity. */
+    public @Nullable String getSettingsActivity() {
+        // This logic is not used by the top entry but rather what activity should
+        // be launched from the settings screen.
+        for (CredentialProviderInfo cpi : mCredentialProviderInfos) {
+            final CharSequence settingsActivity = cpi.getSettingsActivity();
+            if (!TextUtils.isEmpty(settingsActivity)) {
+                return String.valueOf(settingsActivity);
+            }
+        }
+
+        if (mAutofillServiceInfo != null) {
+            final String settingsActivity = mAutofillServiceInfo.getSettingsActivity();
+            if (!TextUtils.isEmpty(settingsActivity)) {
+                return settingsActivity;
+            }
+        }
+
+        return null;
+    }
+
     /** Returns the app icon. */
     @Nullable
     public Drawable getAppIcon(@NonNull Context context, int userId) {
diff --git a/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
new file mode 100644
index 0000000..495c104
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.applications.credentials;
+
+
+import com.android.settings.SettingsActivity;
+
+/** Standalone activity used to launch a {@link DefaultCombinedPicker} fragment. */
+public class CredentialsPickerActivity extends SettingsActivity {
+
+    @Override
+    protected boolean isValidFragment(String fragmentName) {
+        return super.isValidFragment(fragmentName)
+                || DefaultCombinedPicker.class.getName().equals(fragmentName);
+    }
+}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
index 59c33b2..47a89ec 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
@@ -16,11 +16,10 @@
 
 package com.android.settings.applications.credentials;
 
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.credentials.CredentialManager;
 import android.credentials.CredentialProviderInfo;
@@ -29,16 +28,19 @@
 import android.service.autofill.AutofillService;
 import android.service.autofill.AutofillServiceInfo;
 import android.text.TextUtils;
-import android.util.Log;
 import android.view.autofill.AutofillManager;
 
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
 import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
 import com.android.settingslib.applications.DefaultAppInfo;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
+public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController
+        implements Preference.OnPreferenceClickListener {
 
     private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
     private static final String TAG = "DefaultCombinedPreferenceController";
@@ -73,18 +75,55 @@
 
     @Override
     protected Intent getSettingIntent(DefaultAppInfo info) {
-        if (info == null) {
-            return null;
+        // Despite this method being called getSettingIntent this intent actually
+        // opens the primary picker. This is so that we can swap the cog and the left
+        // hand side presses to align the UX.
+        return new Intent(mContext, CredentialsPickerActivity.class);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        final String prefKey = getPreferenceKey();
+        final Preference preference = screen.findPreference(prefKey);
+        if (preference != null) {
+            preference.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) this);
         }
-        final AutofillSettingIntentProvider intentProvider =
-                new AutofillSettingIntentProvider(mContext, getUser(), info.getKey());
-        return intentProvider.getIntent();
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        // Get the selected provider.
+        final CombinedProviderInfo topProvider = getTopProvider();
+        if (topProvider == null) {
+            return false;
+        }
+
+        // If the top provider has a defined Credential Manager settings
+        // provider then we should open that up.
+        final String settingsActivity = topProvider.getSettingsActivity();
+        if (!TextUtils.isEmpty(settingsActivity)) {
+            final Intent intent =
+                    new Intent(Intent.ACTION_MAIN)
+                            .setComponent(
+                                    new ComponentName(
+                                            topProvider.getPackageName(), settingsActivity));
+            startActivity(intent);
+            return true;
+        }
+
+        return false;
+    }
+
+    private @Nullable CombinedProviderInfo getTopProvider() {
+        List<CombinedProviderInfo> providers = getAllProviders(getUser());
+        return CombinedProviderInfo.getTopProvider(providers);
     }
 
     @Override
     protected DefaultAppInfo getDefaultAppInfo() {
-        List<CombinedProviderInfo> providers = getAllProviders(getUser());
-        CombinedProviderInfo topProvider = CombinedProviderInfo.getTopProvider(providers);
+        CombinedProviderInfo topProvider = getTopProvider();
         if (topProvider != null) {
             ServiceInfo brandingService = topProvider.getBrandingService();
             if (brandingService == null) {
@@ -138,53 +177,6 @@
         return true;
     }
 
-    /** Provides Intent to setting activity for the specified autofill service. */
-    static final class AutofillSettingIntentProvider {
-
-        private final String mKey;
-        private final Context mContext;
-        private final int mUserId;
-
-        public AutofillSettingIntentProvider(Context context, int userId, String key) {
-            mKey = key;
-            mContext = context;
-            mUserId = userId;
-        }
-
-        public Intent getIntent() {
-            final List<ResolveInfo> resolveInfos =
-                    mContext.getPackageManager()
-                            .queryIntentServicesAsUser(
-                                    AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId);
-
-            for (ResolveInfo resolveInfo : resolveInfos) {
-                final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-
-                // If there are multiple autofill services then pick the first one.
-                if (mKey != null && mKey.startsWith(serviceInfo.packageName)) {
-                    final String settingsActivity;
-                    try {
-                        settingsActivity =
-                                new AutofillServiceInfo(mContext, serviceInfo)
-                                        .getSettingsActivity();
-                    } catch (SecurityException e) {
-                        // Service does not declare the proper permission, ignore it.
-                        Log.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
-                        return null;
-                    }
-                    if (TextUtils.isEmpty(settingsActivity)) {
-                        return null;
-                    }
-                    return new Intent(Intent.ACTION_MAIN)
-                            .setComponent(
-                                    new ComponentName(serviceInfo.packageName, settingsActivity));
-                }
-            }
-
-            return null;
-        }
-    }
-
     protected int getUser() {
         return UserHandle.myUserId();
     }
diff --git a/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt b/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt
index d606f3c..990e221 100644
--- a/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt
+++ b/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt
@@ -36,15 +36,6 @@
         return "default_credman_autofill_private"
     }
 
-    override fun getSettingIntent(info: DefaultAppInfo ?): Intent ? {
-        if (info == null) {
-            return null
-        }
-        return userHandle?.let { handle ->
-            AutofillSettingIntentProvider(mContext, handle.identifier, info.key).intent
-        } ?: null
-    }
-
     override fun startActivity(intent: Intent) {
         userHandle?.let { handle ->
             mContext.startActivityAsUser(intent, handle)
diff --git a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
index eb0aa7a..f7ca204 100644
--- a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
@@ -21,7 +21,6 @@
 import android.os.UserHandle;
 
 import com.android.settings.Utils;
-import com.android.settingslib.applications.DefaultAppInfo;
 
 public class DefaultWorkCombinedPreferenceController extends DefaultCombinedPreferenceController {
     private final UserHandle mUserHandle;
@@ -45,17 +44,6 @@
     }
 
     @Override
-    protected Intent getSettingIntent(DefaultAppInfo info) {
-        if (info == null) {
-            return null;
-        }
-        final AutofillSettingIntentProvider intentProvider =
-                new AutofillSettingIntentProvider(
-                        mContext, mUserHandle.getIdentifier(), info.getKey());
-        return intentProvider.getIntent();
-    }
-
-    @Override
     protected void startActivity(Intent intent) {
         mContext.startActivityAsUser(intent, mUserHandle);
     }
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
index 0bfb87d..d3bc977 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
@@ -182,7 +182,7 @@
      * Called when a fingerprint image has been acquired, but wasn't processed yet.
      */
     public void onAcquired(boolean isAcquiredGood) {
-        if (mListener != null && mTotalSteps != -1) {
+        if (mListener != null) {
             mListener.onAcquired(isAcquiredGood && animateIfLastStep());
         }
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
index 21f1c0e..90cf779 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
@@ -16,8 +16,12 @@
 
 package com.android.settings.connecteddevice.audiosharing;
 
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
@@ -26,14 +30,18 @@
 import com.android.settings.flags.Flags;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
 
-public abstract class AudioSharingBasePreferenceController extends BasePreferenceController {
+public abstract class AudioSharingBasePreferenceController extends BasePreferenceController
+        implements DefaultLifecycleObserver {
+    private final BluetoothAdapter mBluetoothAdapter;
     private final LocalBluetoothManager mBtManager;
     protected final LocalBluetoothLeBroadcast mBroadcast;
     protected Preference mPreference;
 
     public AudioSharingBasePreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         mBtManager = Utils.getLocalBtManager(context);
         mBroadcast =
                 mBtManager == null
@@ -43,28 +51,40 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return mBtManager != null && Flags.enableLeAudioSharing()
-                ? AVAILABLE
-                : UNSUPPORTED_ON_DEVICE;
+        return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
         mPreference = screen.findPreference(getPreferenceKey());
-        updateVisibility(isBroadcasting());
     }
 
-    /**
-     * Update the visibility of the preference.
-     *
-     * @param isVisible the latest visibility state for the preference.
-     */
-    public void updateVisibility(boolean isVisible) {
-        mPreference.setVisible(isVisible);
+    @Override
+    public void onStart(@NonNull LifecycleOwner owner) {
+        if (isAvailable()) {
+            updateVisibility();
+        }
+    }
+
+    /** Update the visibility of the preference. */
+    protected void updateVisibility() {
+        if (mPreference != null) {
+            var unused =
+                    ThreadUtils.postOnBackgroundThread(
+                            () -> {
+                                boolean isVisible = isBroadcasting() && isBluetoothStateOn();
+                                ThreadUtils.postOnMainThread(
+                                        () -> mPreference.setVisible(isVisible));
+                            });
+        }
     }
 
     protected boolean isBroadcasting() {
         return mBroadcast != null && mBroadcast.isEnabled(null);
     }
+
+    protected boolean isBluetoothStateOn() {
+        return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
+    }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
index 7f90ceb..52a8f18 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -93,14 +93,14 @@
     }
 
     @Override
-    public void onSwitchBarChanged(boolean newState) {
-        updateVisibilityForAttachedPreferences(newState);
+    public void onSwitchBarChanged() {
+        updateVisibilityForAttachedPreferences();
     }
 
-    private void updateVisibilityForAttachedPreferences(boolean isVisible) {
-        mAudioSharingDeviceVolumeGroupController.updateVisibility(isVisible);
-        mCallsAndAlarmsPreferenceController.updateVisibility(isVisible);
-        mAudioSharingNamePreferenceController.updateVisibility(isVisible);
-        mAudioStreamsCategoryController.updateVisibility(isVisible);
+    private void updateVisibilityForAttachedPreferences() {
+        mAudioSharingDeviceVolumeGroupController.updateVisibility();
+        mCallsAndAlarmsPreferenceController.updateVisibility();
+        mAudioSharingNamePreferenceController.updateVisibility();
+        mAudioStreamsCategoryController.updateVisibility();
     }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
index 90054d4..bdaa534 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
@@ -27,7 +27,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.DefaultLifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceGroup;
@@ -48,7 +47,7 @@
 import java.util.concurrent.Executors;
 
 public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePreferenceController
-        implements DefaultLifecycleObserver, DevicePreferenceCallback {
+        implements DevicePreferenceCallback {
 
     private static final String TAG = "AudioSharingDeviceVolumeGroupController";
     private static final String KEY = "audio_sharing_device_volume_group";
@@ -162,6 +161,7 @@
 
     @Override
     public void onStart(@NonNull LifecycleOwner owner) {
+        super.onStart(owner);
         if (mAssistant == null) {
             Log.d(TAG, "onStart() Broadcast or assistant is not supported on this device");
             return;
@@ -176,6 +176,7 @@
 
     @Override
     public void onStop(@NonNull LifecycleOwner owner) {
+        super.onStop(owner);
         if (mAssistant == null) {
             Log.d(TAG, "onStop() Broadcast or assistant is not supported on this device");
             return;
@@ -233,10 +234,12 @@
     }
 
     @Override
-    public void updateVisibility(boolean isVisible) {
-        super.updateVisibility(isVisible);
+    public void updateVisibility() {
         if (mPreferenceGroup != null) {
-            mPreferenceGroup.setVisible(mPreferenceGroup.getPreferenceCount() > 0);
+            mPreferenceGroup.setVisible(false);
+            if (mPreferenceGroup.getPreferenceCount() > 0) {
+                super.updateVisibility();
+            }
         }
     }
 
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
index 8336691..36f66ff 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -19,16 +19,13 @@
 import android.content.Context;
 
 import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 
 import com.android.settings.widget.ValidatedEditTextPreference;
 
 public class AudioSharingNamePreferenceController extends AudioSharingBasePreferenceController
-        implements ValidatedEditTextPreference.Validator,
-                Preference.OnPreferenceChangeListener,
-                DefaultLifecycleObserver {
+        implements ValidatedEditTextPreference.Validator, Preference.OnPreferenceChangeListener {
 
     private static final String TAG = "AudioSharingNamePreferenceController";
 
@@ -59,11 +56,13 @@
 
     @Override
     public void onStart(@NonNull LifecycleOwner owner) {
+        super.onStart(owner);
         // TODO
     }
 
     @Override
     public void onStop(@NonNull LifecycleOwner owner) {
+        super.onStop(owner);
         // TODO
     }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 96a5579..469a387 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -31,6 +31,7 @@
 import android.widget.CompoundButton.OnCheckedChangeListener;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.DefaultLifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 
@@ -58,19 +59,20 @@
     private static final String PREF_KEY = "audio_sharing_main_switch";
 
     interface OnSwitchBarChangedListener {
-        void onSwitchBarChanged(boolean newState);
+        void onSwitchBarChanged();
     }
 
     private final SettingsMainSwitchBar mSwitchBar;
     private final BluetoothAdapter mBluetoothAdapter;
-    private final IntentFilter mIntentFilter;
     private final LocalBluetoothManager mBtManager;
     private final LocalBluetoothLeBroadcast mBroadcast;
     private final LocalBluetoothLeBroadcastAssistant mAssistant;
     private final Executor mExecutor;
     private final OnSwitchBarChangedListener mListener;
     private DashboardFragment mFragment;
+    @VisibleForTesting IntentFilter mIntentFilter;
 
+    @VisibleForTesting
     BroadcastReceiver mReceiver =
             new BroadcastReceiver() {
                 @Override
@@ -80,6 +82,7 @@
                             intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
                     mSwitchBar.setChecked(isBroadcasting());
                     mSwitchBar.setEnabled(adapterState == BluetoothAdapter.STATE_ON);
+                    mListener.onSwitchBarChanged();
                 }
             };
 
@@ -346,15 +349,19 @@
     }
 
     private void updateSwitch() {
-        ThreadUtils.postOnMainThread(
-                () -> {
-                    boolean isBroadcasting = isBroadcasting();
-                    if (mSwitchBar.isChecked() != isBroadcasting) {
-                        mSwitchBar.setChecked(isBroadcasting);
-                    }
-                    mSwitchBar.setEnabled(true);
-                    mListener.onSwitchBarChanged(isBroadcasting);
-                });
+        var unused =
+                ThreadUtils.postOnBackgroundThread(
+                        () -> {
+                            boolean isBroadcasting = isBroadcasting();
+                            ThreadUtils.postOnMainThread(
+                                    () -> {
+                                        if (mSwitchBar.isChecked() != isBroadcasting) {
+                                            mSwitchBar.setChecked(isBroadcasting);
+                                        }
+                                        mSwitchBar.setEnabled(true);
+                                        mListener.onSwitchBarChanged();
+                                    });
+                        });
     }
 
     private boolean isBroadcasting() {
@@ -376,7 +383,7 @@
                 TAG,
                 "Add broadcast with broadcastId: "
                         + broadcastMetadata.getBroadcastId()
-                        + "to the device: "
+                        + " to the device: "
                         + sink.getAnonymizedAddress());
         mAssistant.addSource(sink, broadcastMetadata, /* isGroupOp= */ false);
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
index a6adf8a..b3d676c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
@@ -22,7 +22,6 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.PreferenceScreen;
 
@@ -31,6 +30,7 @@
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -39,7 +39,7 @@
 
 /** PreferenceController to control the dialog to choose the active device for calls and alarms */
 public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController
-        implements BluetoothCallback, DefaultLifecycleObserver {
+        implements BluetoothCallback {
 
     private static final String TAG = "CallsAndAlarmsPreferenceController";
     private static final String PREF_KEY = "calls_and_alarms";
@@ -86,6 +86,7 @@
 
     @Override
     public void onStart(@NonNull LifecycleOwner owner) {
+        super.onStart(owner);
         if (mLocalBtManager != null) {
             mLocalBtManager.getEventManager().registerCallback(this);
         }
@@ -93,25 +94,46 @@
 
     @Override
     public void onStop(@NonNull LifecycleOwner owner) {
+        super.onStop(owner);
         if (mLocalBtManager != null) {
             mLocalBtManager.getEventManager().unregisterCallback(this);
         }
     }
 
     @Override
-    public void updateVisibility(boolean isVisible) {
-        super.updateVisibility(isVisible);
-        if (isVisible && mPreference != null) {
-            updateDeviceItemsInSharingSession();
-            // mDeviceItemsInSharingSession is ordered. The active device is the first place if
-            // exits.
-            if (!mDeviceItemsInSharingSession.isEmpty()
-                    && mDeviceItemsInSharingSession.get(0).isActive()) {
-                mPreference.setSummary(mDeviceItemsInSharingSession.get(0).getName());
-            } else {
-                mPreference.setSummary("");
-            }
-        }
+    public void updateVisibility() {
+        if (mPreference == null) return;
+        var unused =
+                ThreadUtils.postOnBackgroundThread(
+                        () -> {
+                            boolean isVisible = isBroadcasting() && isBluetoothStateOn();
+                            if (!isVisible) {
+                                ThreadUtils.postOnMainThread(() -> mPreference.setVisible(false));
+                            } else {
+                                updateDeviceItemsInSharingSession();
+                                // mDeviceItemsInSharingSession is ordered. The active device is the
+                                // first
+                                // place if exits.
+                                if (!mDeviceItemsInSharingSession.isEmpty()
+                                        && mDeviceItemsInSharingSession.get(0).isActive()) {
+                                    ThreadUtils.postOnMainThread(
+                                            () -> {
+                                                mPreference.setVisible(true);
+                                                mPreference.setSummary(
+                                                        mDeviceItemsInSharingSession
+                                                                .get(0)
+                                                                .getName());
+                                            });
+                                } else {
+                                    ThreadUtils.postOnMainThread(
+                                            () -> {
+                                                mPreference.setVisible(true);
+                                                mPreference.setSummary(
+                                                        "No active device in sharing");
+                                            });
+                                }
+                            }
+                        });
     }
 
     @Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
index f80fdab..f47526f 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
@@ -22,7 +22,6 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 
 import com.android.settings.bluetooth.Utils;
@@ -38,8 +37,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
-public class AudioStreamsCategoryController extends AudioSharingBasePreferenceController
-        implements DefaultLifecycleObserver {
+public class AudioStreamsCategoryController extends AudioSharingBasePreferenceController {
     private static final String TAG = "AudioStreamsCategoryController";
     private static final boolean DEBUG = BluetoothUtils.D;
     private final LocalBluetoothManager mLocalBtManager;
@@ -50,7 +48,7 @@
                 public void onActiveDeviceChanged(
                         @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
                     if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
-                        updateVisibility(isBroadcasting());
+                        updateVisibility();
                     }
                 }
             };
@@ -63,14 +61,15 @@
 
     @Override
     public void onStart(@NonNull LifecycleOwner owner) {
+        super.onStart(owner);
         if (mLocalBtManager != null) {
             mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
         }
-        updateVisibility(isBroadcasting());
     }
 
     @Override
     public void onStop(@NonNull LifecycleOwner owner) {
+        super.onStop(owner);
         if (mLocalBtManager != null) {
             mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
         }
@@ -84,21 +83,28 @@
     }
 
     @Override
-    public void updateVisibility(boolean isBroadcasting) {
+    public void updateVisibility() {
+        if (mPreference == null) return;
         mExecutor.execute(
                 () -> {
                     boolean hasActiveLe =
                             AudioSharingUtils.getActiveSinkOnAssistant(mLocalBtManager).isPresent();
+                    boolean isBroadcasting = isBroadcasting();
+                    boolean isBluetoothOn = isBluetoothStateOn();
                     if (DEBUG) {
                         Log.d(
                                 TAG,
                                 "updateVisibility() isBroadcasting : "
                                         + isBroadcasting
                                         + " hasActiveLe : "
-                                        + hasActiveLe);
+                                        + hasActiveLe
+                                        + " is BT on : "
+                                        + isBluetoothOn);
                     }
                     ThreadUtils.postOnMainThread(
-                            () -> super.updateVisibility(hasActiveLe && !isBroadcasting));
+                            () ->
+                                    mPreference.setVisible(
+                                            isBluetoothOn && hasActiveLe && !isBroadcasting));
                 });
     }
 }
diff --git a/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
index a6dd732..0b88657 100644
--- a/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
+++ b/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
@@ -101,7 +101,7 @@
         final LockPatternUtils lpu = new LockPatternUtils(this);
         if (StorageManager.isFileEncrypted()) {
             for (UserInfo user : getSystemService(UserManager.class).getUsers()) {
-                if (StorageManager.isUserKeyUnlocked(user.id)) {
+                if (StorageManager.isCeStorageUnlocked(user.id)) {
                     continue;
                 }
                 if (!lpu.isSecure(user.id)) {
diff --git a/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
index bf16ab0..f55f822 100644
--- a/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
+++ b/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
@@ -83,7 +83,7 @@
         final LockPatternUtils lpu = new LockPatternUtils(this);
         if (StorageManager.isFileEncrypted()) {
             for (UserInfo user : getSystemService(UserManager.class).getUsers()) {
-                if (StorageManager.isUserKeyUnlocked(user.id)) {
+                if (StorageManager.isCeStorageUnlocked(user.id)) {
                     continue;
                 }
                 if (!lpu.isSecure(user.id)) {
diff --git a/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
index 4a25238..15f2094 100644
--- a/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
+++ b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
@@ -95,6 +95,9 @@
 
     /** Suggest a list of package to set as POLICY_REJECT. */
     public void setDenylist(Set<Integer> denylistTargetUids) {
+        if (denylistTargetUids == null) {
+            return;
+        }
         final Set<Integer> manualDenylistUids = getDenylistAllUids(getManualDenylistPref());
         denylistTargetUids.removeAll(manualDenylistUids);
 
@@ -105,27 +108,40 @@
             return;
         }
 
-        // Store target denied uids into DynamicDenylistPref.
-        final SharedPreferences.Editor editor = getDynamicDenylistPref().edit();
-        editor.clear();
-        denylistTargetUids.forEach(
-                uid -> editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND));
-        editor.apply();
-
+        final ArraySet<Integer> failedUids = new ArraySet<>();
         synchronized (mLock) {
             // Set new added UIDs into REJECT policy.
             for (int uid : denylistTargetUids) {
                 if (!lastDynamicDenylistUids.contains(uid)) {
-                    mNetworkPolicyManager.setUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
+                    try {
+                        mNetworkPolicyManager.setUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
+                    } catch (Exception e) {
+                        Log.e(TAG, "failed to setUidPolicy(REJECT) for " + uid, e);
+                        failedUids.add(uid);
+                    }
                 }
             }
             // Unset removed UIDs back to NONE policy.
             for (int uid : lastDynamicDenylistUids) {
                 if (!denylistTargetUids.contains(uid)) {
-                    mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE);
+                    try {
+                        mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE);
+                    } catch (Exception e) {
+                        Log.e(TAG, "failed to setUidPolicy(NONE) for " + uid, e);
+                    }
                 }
             }
         }
+
+        // Store target denied uids into DynamicDenylistPref.
+        final SharedPreferences.Editor editor = getDynamicDenylistPref().edit();
+        editor.clear();
+        denylistTargetUids.forEach(uid -> {
+            if (!failedUids.contains(uid)) {
+                editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND);
+            }
+        });
+        editor.apply();
     }
 
     /** Return true if the target uid is in {@link #getManualDenylistPref()}. */
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index 10fdc98..e79c1b2 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -98,7 +98,6 @@
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
 
         controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner));
-        controllers.add(new TetherPreferenceController(context, lifecycle));
         controllers.add(vpnPreferenceController);
         if (internetPreferenceController != null) {
             controllers.add(internetPreferenceController);
diff --git a/src/com/android/settings/network/TetherPreferenceController.java b/src/com/android/settings/network/TetherPreferenceController.java
deleted file mode 100644
index f9e5a43..0000000
--- a/src/com/android/settings/network/TetherPreferenceController.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.network;
-
-import static android.os.UserManager.DISALLOW_CONFIG_TETHERING;
-
-import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfRestrictionEnforced;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.TetheringManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.core.FeatureFlags;
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settingslib.TetherUtil;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnCreate;
-import com.android.settingslib.core.lifecycle.events.OnDestroy;
-import com.android.settingslib.core.lifecycle.events.OnPause;
-import com.android.settingslib.core.lifecycle.events.OnResume;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-public class TetherPreferenceController extends AbstractPreferenceController implements
-        PreferenceControllerMixin, LifecycleObserver, OnCreate, OnResume, OnPause, OnDestroy {
-
-    private static final String TAG = "TetherPreferenceController";
-    private static final String KEY_TETHER_SETTINGS = "tether_settings";
-
-    private final boolean mAdminDisallowedTetherConfig;
-    private final AtomicReference<BluetoothPan> mBluetoothPan;
-    private final BluetoothAdapter mBluetoothAdapter;
-    private final TetheringManager mTetheringManager;
-    @VisibleForTesting
-    final BluetoothProfile.ServiceListener mBtProfileServiceListener =
-            new android.bluetooth.BluetoothProfile.ServiceListener() {
-                public void onServiceConnected(int profile, BluetoothProfile proxy) {
-                    if (mBluetoothPan.get() == null) {
-                        mBluetoothPan.set((BluetoothPan) proxy);
-                    }
-                    updateSummary();
-                }
-
-                public void onServiceDisconnected(int profile) {
-                    updateSummary();
-                }
-            };
-
-    private SettingObserver mAirplaneModeObserver;
-    private Preference mPreference;
-    private TetherBroadcastReceiver mTetherReceiver;
-    private BroadcastReceiver mBluetoothStateReceiver;
-
-    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
-    TetherPreferenceController() {
-        super(null);
-        mAdminDisallowedTetherConfig = false;
-        mBluetoothPan = new AtomicReference<>();
-        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        mTetheringManager = null;
-    }
-
-    public TetherPreferenceController(Context context, Lifecycle lifecycle) {
-        super(context);
-        mBluetoothPan = new AtomicReference<>();
-        mAdminDisallowedTetherConfig = isTetherConfigDisallowed(context);
-        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        mTetheringManager = context.getSystemService(TetheringManager.class);
-        if (lifecycle != null) {
-            lifecycle.addObserver(this);
-        }
-    }
-
-    @Override
-    public void displayPreference(PreferenceScreen screen) {
-        super.displayPreference(screen);
-        mPreference = screen.findPreference(KEY_TETHER_SETTINGS);
-        if (mPreference != null && !mAdminDisallowedTetherConfig) {
-            mPreference.setTitle(
-                    com.android.settingslib.Utils.getTetheringLabel(mTetheringManager));
-        }
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return TetherUtil.isTetherAvailable(mContext)
-                && !FeatureFlagUtils.isEnabled(mContext, FeatureFlags.TETHER_ALL_IN_ONE);
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        updateSummary();
-    }
-
-    @Override
-    public String getPreferenceKey() {
-        return KEY_TETHER_SETTINGS;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        if (mBluetoothAdapter != null &&
-            mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
-            mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener,
-                    BluetoothProfile.PAN);
-        }
-        if (mBluetoothStateReceiver == null) {
-            mBluetoothStateReceiver = new BluetoothStateReceiver();
-            mContext.registerReceiver(
-                    mBluetoothStateReceiver,
-                    new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
-        }
-    }
-
-    @Override
-    public void onResume() {
-        if (mAirplaneModeObserver == null) {
-            mAirplaneModeObserver = new SettingObserver();
-        }
-        if (mTetherReceiver == null) {
-            mTetherReceiver = new TetherBroadcastReceiver();
-        }
-        mContext.registerReceiver(
-                mTetherReceiver, new IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED));
-        mContext.getContentResolver()
-                .registerContentObserver(mAirplaneModeObserver.uri, false, mAirplaneModeObserver);
-    }
-
-    @Override
-    public void onPause() {
-        if (mAirplaneModeObserver != null) {
-            mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver);
-        }
-        if (mTetherReceiver != null) {
-            mContext.unregisterReceiver(mTetherReceiver);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        final BluetoothProfile profile = mBluetoothPan.getAndSet(null);
-        if (profile != null && mBluetoothAdapter != null) {
-            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile);
-        }
-        if (mBluetoothStateReceiver != null) {
-            mContext.unregisterReceiver(mBluetoothStateReceiver);
-            mBluetoothStateReceiver = null;
-        }
-    }
-
-    public static boolean isTetherConfigDisallowed(Context context) {
-        return checkIfRestrictionEnforced(
-                context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null;
-    }
-
-    @VisibleForTesting
-    void updateSummary() {
-        if (mPreference == null) {
-            // Preference is not ready yet.
-            return;
-        }
-        String[] allTethered = mTetheringManager.getTetheredIfaces();
-        String[] wifiTetherRegex = mTetheringManager.getTetherableWifiRegexs();
-        String[] bluetoothRegex = mTetheringManager.getTetherableBluetoothRegexs();
-
-        boolean hotSpotOn = false;
-        boolean tetherOn = false;
-        if (allTethered != null) {
-            if (wifiTetherRegex != null) {
-                for (String tethered : allTethered) {
-                    for (String regex : wifiTetherRegex) {
-                        if (tethered.matches(regex)) {
-                            hotSpotOn = true;
-                            break;
-                        }
-                    }
-                }
-            }
-            if (allTethered.length > 1) {
-                // We have more than 1 tethered connection
-                tetherOn = true;
-            } else if (allTethered.length == 1) {
-                // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of
-                // tether.
-                tetherOn = !hotSpotOn;
-            } else {
-                // No tethered connection.
-                tetherOn = false;
-            }
-        }
-        if (!tetherOn
-                && bluetoothRegex != null && bluetoothRegex.length > 0
-                && mBluetoothAdapter != null
-                && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
-            // Check bluetooth state. It's not included in mTetheringManager.getTetheredIfaces.
-            final BluetoothPan pan = mBluetoothPan.get();
-            tetherOn = pan != null && pan.isTetheringOn();
-        }
-        if (!hotSpotOn && !tetherOn) {
-            // Both off
-            updateSummaryToOff();
-        } else if (hotSpotOn && tetherOn) {
-            // Both on
-            mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on);
-        } else if (hotSpotOn) {
-            mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off);
-        } else {
-            mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on);
-        }
-    }
-
-    private void updateSummaryToOff() {
-        if (mPreference == null) {
-            // Preference is not ready yet.
-            return;
-        }
-        mPreference.setSummary(R.string.tether_preference_summary_off);
-    }
-
-    class SettingObserver extends ContentObserver {
-
-        public final Uri uri;
-
-        public SettingObserver() {
-            super(new Handler());
-            uri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            super.onChange(selfChange, uri);
-            if (this.uri.equals(uri)) {
-                boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
-                if (isAirplaneMode) {
-                    // Airplane mode is on. Update summary to say tether is OFF directly. We cannot
-                    // go through updateSummary() because turning off tether takes time, and we
-                    // might still get "ON" status when rerun updateSummary(). So, just say it's off
-                    updateSummaryToOff();
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    class TetherBroadcastReceiver extends BroadcastReceiver {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            updateSummary();
-        }
-
-    }
-
-    private class BluetoothStateReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            Log.i(TAG, "onReceive: action: " + action);
-
-            if (TextUtils.equals(action, BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                final int state =
-                        intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-                Log.i(TAG, "onReceive: state: " + BluetoothAdapter.nameForState(state));
-                final BluetoothProfile profile = mBluetoothPan.get();
-                switch(state) {
-                    case BluetoothAdapter.STATE_ON:
-                        if (profile == null && mBluetoothAdapter != null) {
-                            mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener,
-                                    BluetoothProfile.PAN);
-                        }
-                        break;
-                }
-            }
-        }
-    }
-}
diff --git a/src/com/android/settings/network/TetherPreferenceController.kt b/src/com/android/settings/network/TetherPreferenceController.kt
new file mode 100644
index 0000000..8f55f50
--- /dev/null
+++ b/src/com/android/settings/network/TetherPreferenceController.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.network
+
+import android.content.Context
+import android.net.TetheringManager
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.FeatureFlagUtils
+import androidx.annotation.StringRes
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.core.FeatureFlags
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.settingslib.TetherUtil
+import com.android.settingslib.Utils
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class TetherPreferenceController(context: Context, key: String) :
+    BasePreferenceController(context, key) {
+
+    private val tetheredRepository = TetheredRepository(context)
+    private val tetheringManager = mContext.getSystemService(TetheringManager::class.java)!!
+
+    private var preference: Preference? = null
+
+    override fun getAvailabilityStatus() =
+        if (TetherUtil.isTetherAvailable(mContext)
+            && !FeatureFlagUtils.isEnabled(mContext, FeatureFlags.TETHER_ALL_IN_ONE)
+        ) {
+            AVAILABLE
+        } else {
+            CONDITIONALLY_UNAVAILABLE
+        }
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        super.displayPreference(screen)
+        preference = screen.findPreference(preferenceKey)
+    }
+
+    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                getTitleResId()?.let { preference?.setTitle(it) }
+            }
+        }
+
+        tetheredRepository.tetheredTypesFlow().collectLatestWithLifecycle(viewLifecycleOwner) {
+            preference?.setSummary(getSummaryResId(it))
+        }
+    }
+
+    private suspend fun getTitleResId(): Int? = withContext(Dispatchers.Default) {
+        if (isTetherConfigDisallowed(mContext)) null
+        else Utils.getTetheringLabel(tetheringManager)
+    }
+
+    @VisibleForTesting
+    @StringRes
+    fun getSummaryResId(tetheredTypes: Set<Int>): Int {
+        val hotSpotOn = TetheringManager.TETHERING_WIFI in tetheredTypes
+        val tetherOn = tetheredTypes.any { it != TetheringManager.TETHERING_WIFI }
+        return when {
+            hotSpotOn && tetherOn -> R.string.tether_settings_summary_hotspot_on_tether_on
+            hotSpotOn -> R.string.tether_settings_summary_hotspot_on_tether_off
+            tetherOn -> R.string.tether_settings_summary_hotspot_off_tether_on
+            else -> R.string.tether_preference_summary_off
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        fun isTetherConfigDisallowed(context: Context?): Boolean =
+            RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+                context, UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()
+            ) != null
+    }
+}
diff --git a/src/com/android/settings/network/TetheredRepository.kt b/src/com/android/settings/network/TetheredRepository.kt
new file mode 100644
index 0000000..f18bdca
--- /dev/null
+++ b/src/com/android/settings/network/TetheredRepository.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.network
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothPan
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.content.IntentFilter
+import android.net.TetheringInterface
+import android.net.TetheringManager
+import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
+
+class TetheredRepository(private val context: Context) {
+    private val tetheringManager = context.getSystemService(TetheringManager::class.java)!!
+
+    private val adapter = context.getSystemService(BluetoothManager::class.java)!!.adapter
+
+    fun tetheredTypesFlow(): Flow<Set<Int>> =
+        combine(
+            tetheredInterfacesFlow(),
+            isBluetoothTetheringOnFlow(),
+        ) { tetheringInterfaces, isBluetoothTetheringOn ->
+            val mutableSet = tetheringInterfaces.map { it.type }.toMutableSet()
+            if (isBluetoothTetheringOn) mutableSet += TetheringManager.TETHERING_BLUETOOTH
+            mutableSet
+        }.conflate().flowOn(Dispatchers.Default)
+
+    private fun tetheredInterfacesFlow(): Flow<Set<TetheringInterface>> = callbackFlow {
+        val callback = object : TetheringManager.TetheringEventCallback {
+            override fun onTetheredInterfacesChanged(interfaces: Set<TetheringInterface>) {
+                trySend(interfaces)
+            }
+        }
+
+        tetheringManager.registerTetheringEventCallback(Dispatchers.Default.asExecutor(), callback)
+
+        awaitClose { tetheringManager.unregisterTetheringEventCallback(callback) }
+    }.conflate().flowOn(Dispatchers.Default)
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private fun isBluetoothTetheringOnFlow(): Flow<Boolean> =
+        merge(
+            flowOf(null), // kick an initial value
+            context.broadcastReceiverFlow(IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)),
+        ).flatMapLatest {
+            if (adapter.getState() == BluetoothAdapter.STATE_ON) {
+                isBluetoothPanTetheringOnFlow()
+            } else {
+                flowOf(false)
+            }
+        }.conflate().flowOn(Dispatchers.Default)
+
+    private fun isBluetoothPanTetheringOnFlow() = callbackFlow {
+        var connectedProxy: BluetoothProfile? = null
+
+        val listener = object : BluetoothProfile.ServiceListener {
+            override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
+                connectedProxy = proxy
+                launch(Dispatchers.Default) {
+                    trySend((proxy as BluetoothPan).isTetheringOn)
+                }
+            }
+
+            override fun onServiceDisconnected(profile: Int) {}
+        }
+
+        adapter.getProfileProxy(context, listener, BluetoothProfile.PAN)
+
+        awaitClose {
+            connectedProxy?.let { adapter.closeProfileProxy(BluetoothProfile.PAN, it) }
+        }
+    }.conflate().flowOn(Dispatchers.Default)
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
index 1904cb5..cfd256f 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.companion.CompanionDeviceManager;
 import android.content.Context;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
@@ -49,8 +50,9 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -63,7 +65,6 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.fakes.RoboMenu;
 
-@Ignore("b/313014781")
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {
         com.android.settings.testutils.shadow.ShadowUserManager.class,
@@ -90,12 +91,17 @@
     private UserManager mUserManager;
     @Mock
     private InputManager mInputManager;
+    @Mock
+    private CompanionDeviceManager mCompanionDeviceManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = spy(RuntimeEnvironment.application);
         doReturn(mInputManager).when(mContext).getSystemService(InputManager.class);
+        doReturn(mCompanionDeviceManager).when(mContext)
+                .getSystemService(CompanionDeviceManager.class);
+        when(mCompanionDeviceManager.getAllAssociations()).thenReturn(ImmutableList.of());
         removeInputDeviceWithMatchingBluetoothAddress();
         FakeFeatureFactory.setupForTest();
 
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java
index 88ace91..853d2c48 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java
@@ -33,7 +33,6 @@
 import com.android.settingslib.widget.FooterPreference;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -45,7 +44,7 @@
 public class BluetoothSwitchPreferenceControllerTest {
 
     private static final String BLUETOOTH_INFO_STRING = "When Bluetooth is turned on, your device"
-            + " can communicate with other nearby Bluetooth devices.";
+            + " can communicate with other nearby Bluetooth devices";
     @Mock
     private RestrictionUtils mRestrictionUtils;
     @Mock
@@ -83,7 +82,6 @@
         assertThat(TextUtils.equals(mFooterPreference.getTitle(), text)).isTrue();
     }
 
-    @Ignore("b/313014781")
     @Test
     public void updateText_bluetoothOffScanningOff() {
         Settings.Global.putInt(mContext.getContentResolver(),
@@ -93,7 +91,6 @@
         assertThat(mFooterPreference.getTitle()).isEqualTo(BLUETOOTH_INFO_STRING);
     }
 
-    @Ignore("b/313014781")
     @Test
     public void updateText_bluetoothOnScanningOff() {
         Settings.Global.putInt(mContext.getContentResolver(),
@@ -103,7 +100,6 @@
         assertThat(mFooterPreference.getTitle()).isEqualTo(BLUETOOTH_INFO_STRING);
     }
 
-    @Ignore("b/313014781")
     @Test
     public void updateText_bluetoothOnScanningOn() {
         Settings.Global.putInt(mContext.getContentResolver(),
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
new file mode 100644
index 0000000..0b94061
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audiosharing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Looper;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.widget.Switch;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.flags.Flags;
+import com.android.settings.widget.SettingsMainSwitchBar;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AudioSharingSwitchBarControllerTest {
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Spy Context mContext = ApplicationProvider.getApplicationContext();
+    @Mock private Switch mSwitch;
+
+    private SettingsMainSwitchBar mSwitchBar;
+    private AudioSharingSwitchBarController mController;
+    private AudioSharingSwitchBarController.OnSwitchBarChangedListener mListener;
+    private boolean mOnSwitchBarChanged;
+
+    @Before
+    public void setUp() {
+        mSwitchBar = new SettingsMainSwitchBar(mContext);
+        mOnSwitchBarChanged = false;
+        mListener = () -> mOnSwitchBarChanged = true;
+        mController = new AudioSharingSwitchBarController(mContext, mSwitchBar, mListener);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+    public void bluetoothOff_switchDisabled() {
+        mContext.registerReceiver(
+                mController.mReceiver,
+                mController.mIntentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
+        Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+        intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
+        mContext.sendBroadcast(intent);
+        shadowOf(Looper.getMainLooper()).idle();
+        verify(mSwitch).setEnabled(false);
+        assertThat(mOnSwitchBarChanged).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
index bdf81e4..91d8c7d 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
@@ -40,7 +40,6 @@
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -156,7 +155,6 @@
                         "ScreenTimeoutAnomaly");
     }
 
-    @Ignore("b/313582999")
     @Test
     public void onClick_mainBtnOfAppsAnomaly_selectHighlightSlot() {
         final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent();
@@ -178,7 +176,6 @@
                 .action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, "AppAnomaly");
     }
 
-    @Ignore("b/313582999")
     @Test
     public void onClick_dismissBtnOfAppsAnomaly_keepHighlightSlotIndex() {
         final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent();
diff --git a/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java
deleted file mode 100644
index 99869d8..0000000
--- a/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.network;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.TetheringManager;
-import android.provider.Settings;
-
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.util.ReflectionHelpers;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-@RunWith(RobolectricTestRunner.class)
-public class TetherPreferenceControllerTest {
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private TetheringManager mTetheringManager;
-    @Mock
-    private BluetoothAdapter mBluetoothAdapter;
-    @Mock
-    private Preference mPreference;
-
-    private TetherPreferenceController mController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        doReturn(null).when(mContext)
-                .getSystemService(Context.DEVICE_POLICY_SERVICE);
-        mController = spy(new TetherPreferenceController(mContext, /* lifecycle= */ null));
-        ReflectionHelpers.setField(mController, "mContext", mContext);
-        ReflectionHelpers.setField(mController, "mTetheringManager", mTetheringManager);
-        ReflectionHelpers.setField(mController, "mBluetoothAdapter", mBluetoothAdapter);
-        ReflectionHelpers.setField(mController, "mPreference", mPreference);
-    }
-
-    @Test
-    public void lifeCycle_onCreate_shouldInitBluetoothPan() {
-        when(mBluetoothAdapter.getState()).thenReturn(BluetoothAdapter.STATE_ON);
-        mController.onCreate(null);
-
-        verify(mBluetoothAdapter).getState();
-        verify(mBluetoothAdapter).getProfileProxy(mContext, mController.mBtProfileServiceListener,
-                BluetoothProfile.PAN);
-    }
-
-    @Test
-    public void lifeCycle_onCreate_shouldNotInitBluetoothPanWhenBluetoothOff() {
-        when(mBluetoothAdapter.getState()).thenReturn(BluetoothAdapter.STATE_OFF);
-        mController.onCreate(null);
-
-        verify(mBluetoothAdapter).getState();
-        verifyNoMoreInteractions(mBluetoothAdapter);
-    }
-
-    @Test
-    public void goThroughLifecycle_shouldDestoryBluetoothProfile() {
-        final BluetoothPan pan = mock(BluetoothPan.class);
-        final AtomicReference<BluetoothPan> panRef =
-                ReflectionHelpers.getField(mController, "mBluetoothPan");
-        panRef.set(pan);
-
-        mController.onDestroy();
-
-        verify(mBluetoothAdapter).closeProfileProxy(BluetoothProfile.PAN, pan);
-    }
-
-    @Test
-    public void updateSummary_noPreference_noInteractionWithTetheringManager() {
-        ReflectionHelpers.setField(mController, "mPreference", null);
-        mController.updateSummary();
-        verifyNoMoreInteractions(mTetheringManager);
-    }
-
-    @Test
-    public void updateSummary_wifiTethered_shouldShowHotspotMessage() {
-        when(mTetheringManager.getTetheredIfaces()).thenReturn(new String[]{"123"});
-        when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"123"});
-
-        mController.updateSummary();
-        verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_on_tether_off);
-    }
-
-    @Test
-    public void updateSummary_btThetherOn_shouldShowTetherMessage() {
-        when(mTetheringManager.getTetheredIfaces()).thenReturn(new String[]{"123"});
-        when(mTetheringManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"123"});
-
-        mController.updateSummary();
-        verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_off_tether_on);
-    }
-
-    @Ignore
-    @Test
-    public void updateSummary_tetherOff_shouldShowTetherOffMessage() {
-        when(mTetheringManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"123"});
-        when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"456"});
-
-        mController.updateSummary();
-        verify(mPreference).setSummary(R.string.switch_off_text);
-    }
-
-    @Test
-    public void updateSummary_wifiBtTetherOn_shouldShowHotspotAndTetherMessage() {
-        when(mTetheringManager.getTetheredIfaces()).thenReturn(new String[]{"123", "456"});
-        when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"456"});
-        when(mTetheringManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"23"});
-
-        mController.updateSummary();
-        verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_on_tether_on);
-    }
-
-    @Ignore
-    @Test
-    public void airplaneModeOn_shouldUpdateSummaryToOff() {
-        final Context context = RuntimeEnvironment.application;
-        ReflectionHelpers.setField(mController, "mContext", context);
-
-        Settings.Global.putInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
-
-        mController.onResume();
-
-        verifyNoInteractions(mPreference);
-
-        Settings.Global.putInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
-
-        final ContentObserver observer =
-            ReflectionHelpers.getField(mController, "mAirplaneModeObserver");
-        observer.onChange(true, Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON));
-
-        verify(mPreference).setSummary(R.string.switch_off_text);
-    }
-
-    @Test
-    public void onResume_shouldRegisterTetherReceiver() {
-        when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
-
-        mController.onResume();
-
-        verify(mContext).registerReceiver(
-                any(TetherPreferenceController.TetherBroadcastReceiver.class),
-                any(IntentFilter.class));
-    }
-
-    @Test
-    public void onPause_shouldUnregisterTetherReceiver() {
-        when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
-        mController.onResume();
-
-        mController.onPause();
-
-        verify(mContext)
-            .unregisterReceiver(any(TetherPreferenceController.TetherBroadcastReceiver.class));
-    }
-
-    @Test
-    public void tetherStatesChanged_shouldUpdateSummary() {
-        final Context context = RuntimeEnvironment.application;
-        ReflectionHelpers.setField(mController, "mContext", context);
-        mController.onResume();
-
-        context.sendBroadcast(new Intent(TetheringManager.ACTION_TETHER_STATE_CHANGED));
-
-        shadowMainLooper().idle();
-        verify(mController).updateSummary();
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
index dbd3372..d74b6df 100644
--- a/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
@@ -38,6 +38,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -73,6 +74,7 @@
         assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
     }
 
+    @Ignore("b/313614100")
     @Test
     public void testHandlePreferenceTreeClick() {
         Preference preference = new Preference(mContext);
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
index c693938..d35b608 100644
--- a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
@@ -48,6 +48,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -207,6 +208,7 @@
         verify(mActivity, times(1)).finish();
     }
 
+    @Ignore("b/313615637")
     @Test
     public void testNotFinishedIfAdmin() {
         mUserManager.setIsAdminUser(true);
diff --git a/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt
new file mode 100644
index 0000000..51d2c87
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.network
+
+import android.content.Context
+import android.net.TetheringManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settingslib.TetherUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
+
+@RunWith(AndroidJUnit4::class)
+class TetherPreferenceControllerTest {
+    private lateinit var mockSession: MockitoSession
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val controller = TetherPreferenceController(context, TEST_KEY)
+
+    @Before
+    fun setUp() {
+        mockSession = ExtendedMockito.mockitoSession()
+            .initMocks(this)
+            .mockStatic(TetherUtil::class.java)
+            .strictness(Strictness.LENIENT)
+            .startMocking()
+
+        ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }
+    }
+
+    @After
+    fun tearDown() {
+        mockSession.finishMocking()
+    }
+
+    @Test
+    fun getAvailabilityStatus_whenTetherAvailable() {
+        ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }
+
+        val availabilityStatus = controller.availabilityStatus
+
+        assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE)
+    }
+
+    @Test
+    fun getAvailabilityStatus_whenTetherNotAvailable() {
+        ExtendedMockito.doReturn(false).`when` { TetherUtil.isTetherAvailable(context) }
+
+        val availabilityStatus = controller.availabilityStatus
+
+        assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE)
+    }
+
+    @Test
+    fun getSummaryResId_bothWifiAndBluetoothOn() {
+        val summaryResId = controller.getSummaryResId(
+            setOf(TetheringManager.TETHERING_WIFI, TetheringManager.TETHERING_BLUETOOTH)
+        )
+
+        assertThat(summaryResId).isEqualTo(R.string.tether_settings_summary_hotspot_on_tether_on)
+    }
+
+    @Test
+    fun getSummaryResId_onlyWifiHotspotOn() {
+        val summaryResId = controller.getSummaryResId(setOf(TetheringManager.TETHERING_WIFI))
+
+        assertThat(summaryResId).isEqualTo(R.string.tether_settings_summary_hotspot_on_tether_off)
+    }
+
+    @Test
+    fun getSummaryResId_onlyBluetoothTetheringOn() {
+        val summaryResId = controller.getSummaryResId(setOf(TetheringManager.TETHERING_BLUETOOTH))
+
+        assertThat(summaryResId).isEqualTo(R.string.tether_settings_summary_hotspot_off_tether_on)
+    }
+
+    @Test
+    fun getSummaryResId_allOff() {
+        val summaryResId = controller.getSummaryResId(emptySet())
+
+        assertThat(summaryResId).isEqualTo(R.string.tether_preference_summary_off)
+    }
+
+    private companion object {
+        const val TEST_KEY = "test_key"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/TetheredRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/TetheredRepositoryTest.kt
new file mode 100644
index 0000000..5bd5210
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/TetheredRepositoryTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.network
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothPan
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.net.TetheringInterface
+import android.net.TetheringManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class TetheredRepositoryTest {
+
+    private var tetheringInterfaces: Set<TetheringInterface> = emptySet()
+
+    private var tetheringEventCallback: TetheringManager.TetheringEventCallback? = null
+
+    private val mockTetheringManager = mock<TetheringManager> {
+        on { registerTetheringEventCallback(any(), any()) } doAnswer {
+            tetheringEventCallback = it.arguments[1] as TetheringManager.TetheringEventCallback
+            tetheringEventCallback?.onTetheredInterfacesChanged(tetheringInterfaces)
+        }
+    }
+
+    private val mockBluetoothPan = mock<BluetoothPan> {
+        on { isTetheringOn } doReturn false
+    }
+
+    private val mockBluetoothAdapter = mock<BluetoothAdapter> {
+        on { getProfileProxy(any(), any(), eq(BluetoothProfile.PAN)) } doAnswer {
+            val listener = it.arguments[1] as BluetoothProfile.ServiceListener
+            listener.onServiceConnected(BluetoothProfile.PAN, mockBluetoothPan)
+            true
+        }
+    }
+
+    private val mockBluetoothManager = mock<BluetoothManager> {
+        on { adapter } doReturn mockBluetoothAdapter
+    }
+
+    private val context = mock<Context> {
+        on { getSystemService(TetheringManager::class.java) } doReturn mockTetheringManager
+        on { getSystemService(BluetoothManager::class.java) } doReturn mockBluetoothManager
+    }
+
+    private val repository = TetheredRepository(context)
+
+    @Test
+    fun tetheredTypesFlow_allOff() = runBlocking {
+        val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+        assertThat(tetheredTypes).isEmpty()
+    }
+
+    @Test
+    fun tetheredTypesFlow_wifiHotspotOn(): Unit = runBlocking {
+        tetheringInterfaces = setOf(TetheringInterface(TetheringManager.TETHERING_WIFI, ""))
+
+        val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+        assertThat(tetheredTypes).containsExactly(TetheringManager.TETHERING_WIFI)
+    }
+
+    @Test
+    fun tetheredTypesFlow_usbTetheringTurnOnLater(): Unit = runBlocking {
+        val tetheredTypeDeferred = async {
+            repository.tetheredTypesFlow().mapNotNull {
+                it.singleOrNull()
+            }.firstWithTimeoutOrNull()
+        }
+        delay(100)
+
+        tetheringEventCallback?.onTetheredInterfacesChanged(
+            setOf(TetheringInterface(TetheringManager.TETHERING_USB, ""))
+        )
+
+        assertThat(tetheredTypeDeferred.await()).isEqualTo(TetheringManager.TETHERING_USB)
+    }
+
+    @Test
+    fun tetheredTypesFlow_bluetoothOff(): Unit = runBlocking {
+        mockBluetoothAdapter.stub {
+            on { state } doReturn BluetoothAdapter.STATE_OFF
+        }
+
+        val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+        assertThat(tetheredTypes).isEmpty()
+    }
+
+    @Test
+    fun tetheredTypesFlow_bluetoothOnTetheringOff(): Unit = runBlocking {
+        mockBluetoothAdapter.stub {
+            on { state } doReturn BluetoothAdapter.STATE_ON
+        }
+
+        val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+        assertThat(tetheredTypes).isEmpty()
+    }
+
+    @Test
+    fun tetheredTypesFlow_bluetoothTetheringOn(): Unit = runBlocking {
+        mockBluetoothAdapter.stub {
+            on { state } doReturn BluetoothAdapter.STATE_ON
+        }
+        mockBluetoothPan.stub {
+            on { isTetheringOn } doReturn true
+        }
+
+        val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+        assertThat(tetheredTypes).containsExactly(TetheringManager.TETHERING_BLUETOOTH)
+    }
+}