Merge "Replace "Priority Modes" with "Modes" (Settings)" into main
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 8e09188..c0d67cc 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -201,6 +201,19 @@
                                     + reason
                                     + ", broadcastId = "
                                     + broadcastId);
+                    if (mAssistant == null
+                            || mAssistant.getAllConnectedDevices().stream()
+                                    .anyMatch(
+                                            device -> BluetoothUtils
+                                                    .hasActiveLocalBroadcastSourceForBtDevice(
+                                                            device, mBtManager))) {
+                        Log.d(
+                                TAG,
+                                "Skip handleOnBroadcastReady: null assistant or "
+                                        + "sink has active local source.");
+                        cleanUp();
+                        return;
+                    }
                     handleOnBroadcastReady();
                 }
 
@@ -554,8 +567,7 @@
                         mGroupedConnectedDevices.getOrDefault(
                                 mDeviceItemsForSharing.get(0).getGroupId(), ImmutableList.of()),
                         mBtManager);
-                mGroupedConnectedDevices.clear();
-                mDeviceItemsForSharing.clear();
+                cleanUp();
                 // TODO: Add metric for auto add by intent
                 return;
             }
@@ -565,8 +577,7 @@
                 StartIntentHandleStage.HANDLED.ordinal());
         if (mFragment == null) {
             Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment.");
-            mGroupedConnectedDevices.clear();
-            mDeviceItemsForSharing.clear();
+            cleanUp();
             return;
         }
         showDialog(eventData);
@@ -581,14 +592,12 @@
                                 mGroupedConnectedDevices.getOrDefault(
                                         item.getGroupId(), ImmutableList.of()),
                                 mBtManager);
-                        mGroupedConnectedDevices.clear();
-                        mDeviceItemsForSharing.clear();
+                        cleanUp();
                     }
 
                     @Override
                     public void onCancelClick() {
-                        mGroupedConnectedDevices.clear();
-                        mDeviceItemsForSharing.clear();
+                        cleanUp();
                     }
                 };
         AudioSharingUtils.postOnMainThread(
@@ -657,6 +666,11 @@
                         });
     }
 
+    private void cleanUp() {
+        mGroupedConnectedDevices.clear();
+        mDeviceItemsForSharing.clear();
+    }
+
     private enum StartIntentHandleStage {
         TO_HANDLE,
         HANDLE_AUTO_ADD,
diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt
index 8433715..7ed0c86 100644
--- a/src/com/android/settings/network/apn/ApnRepository.kt
+++ b/src/com/android/settings/network/apn/ApnRepository.kt
@@ -21,10 +21,10 @@
 import android.database.Cursor
 import android.net.Uri
 import android.provider.Telephony
-import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
 import android.util.Log
 import com.android.settings.R
+import com.android.settings.network.telephony.telephonyManager
 import com.android.settingslib.utils.ThreadUtils
 import java.util.Locale
 
@@ -178,12 +178,11 @@
 }
 
 fun Context.getApnIdMap(subId: Int): Map<String, Any> {
-    val subInfo = getSystemService(SubscriptionManager::class.java)!!
-        .getActiveSubscriptionInfo(subId)
-    val carrierId = subInfo.carrierId
+    val telephonyManager = telephonyManager(subId)
+    val carrierId = telephonyManager.simSpecificCarrierId
     return if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
         mapOf(Telephony.Carriers.CARRIER_ID to carrierId)
     } else {
-        mapOf(Telephony.Carriers.NUMERIC to subInfo.mccString + subInfo.mncString)
+        mapOf(Telephony.Carriers.NUMERIC to telephonyManager.simOperator)
     }.also { Log.d(TAG, "[$subId] New APN item with id: $it") }
 }
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index a5cdb95..91874c4 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -41,6 +41,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.preference.Preference;
 
@@ -66,6 +67,8 @@
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.utils.ThreadUtils;
 
+import kotlin.Unit;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -359,6 +362,16 @@
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         collectAirplaneModeAndFinishIfOn(this);
+
+        LifecycleOwner viewLifecycleOwner = getViewLifecycleOwner();
+        new SubscriptionRepository(requireContext())
+                .collectSubscriptionVisible(mSubId, viewLifecycleOwner, (isVisible) -> {
+                    if (!isVisible) {
+                        Log.d(LOG_TAG, "Due to subscription not visible, closes page");
+                        finishFragment();
+                    }
+                    return Unit.INSTANCE;
+                });
     }
 
     @Override
@@ -532,11 +545,6 @@
                 Log.d(LOG_TAG, "Set subInfo to default subInfo.");
             }
         }
-        if (mSubscriptionInfoEntity == null && getActivity() != null) {
-            // If the current subId is not existed, finish it.
-            finishFragment();
-            return;
-        }
         onSubscriptionDetailChanged();
     }
 }
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index cc8c8b4..26ea9b3 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -50,6 +50,31 @@
     fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> =
         context.getSelectableSubscriptionInfoList()
 
+    /** Flow of whether the subscription visible for the given [subId]. */
+    fun isSubscriptionVisibleFlow(subId: Int): Flow<Boolean> {
+        return subscriptionsChangedFlow()
+            .map {
+                val subInfo =
+                    subscriptionManager.availableSubscriptionInfoList?.firstOrNull { subInfo ->
+                        subInfo.subscriptionId == subId
+                    }
+                subInfo != null &&
+                    SubscriptionUtil.isSubscriptionVisible(subscriptionManager, context, subInfo)
+            }
+            .conflate()
+            .onEach { Log.d(TAG, "[$subId] isSubscriptionVisibleFlow: $it") }
+            .flowOn(Dispatchers.Default)
+    }
+
+    /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */
+    fun collectSubscriptionVisible(
+        subId: Int,
+        lifecycleOwner: LifecycleOwner,
+        action: (Boolean) -> Unit,
+    ) {
+        isSubscriptionVisibleFlow(subId).collectLatestWithLifecycle(lifecycleOwner, action = action)
+    }
+
     /** Flow of whether the subscription enabled for the given [subId]. */
     fun isSubscriptionEnabledFlow(subId: Int): Flow<Boolean> {
         if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
index e04763a..9b68970 100644
--- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
@@ -81,6 +81,12 @@
     }
 
     override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            // Sub id could invalid, if this page is opened from external action and no sim is
+            // active.
+            // Ignore this case, since this page will be finished soon.
+            return
+        }
         wifiCallingRepositoryFactory(subId).wifiCallingReadyFlow()
             .collectLatestWithLifecycle(viewLifecycleOwner) { isReady ->
                 preference.isVisible = isReady
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
index 558bc10..5073119 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -443,6 +443,7 @@
                 mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
         when(mBtnView.isEnabled()).thenReturn(true);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
+        when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController =
@@ -469,11 +470,37 @@
     }
 
     @Test
+    public void onPlaybackStarted_hasLocalSource_noDialog() {
+        FeatureFlagUtils.setEnabled(
+                mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+        when(mBtnView.isEnabled()).thenReturn(true);
+        when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
+        BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+        when(state.getBroadcastId()).thenReturn(1);
+        when(mBroadcast.getLatestBroadcastId()).thenReturn(1);
+        when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+        when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
+        doNothing().when(mBroadcast).startPrivateBroadcast();
+        mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        verify(mBroadcast).startPrivateBroadcast();
+        mController.mBroadcastCallback.onPlaybackStarted(0, 0);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mAssistant, never()).addSource(any(), any(), anyBoolean());
+        verify(mFeatureFactory.metricsFeatureProvider, never())
+                .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
+
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).isEmpty();
+    }
+
+    @Test
     public void onPlaybackStarted_showJoinAudioSharingDialog() {
         FeatureFlagUtils.setEnabled(
                 mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
         when(mBtnView.isEnabled()).thenReturn(true);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
+        when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
@@ -519,6 +546,7 @@
                 mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
         when(mBtnView.isEnabled()).thenReturn(true);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
+        when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
@@ -545,6 +573,7 @@
                 mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
         when(mBtnView.isEnabled()).thenReturn(true);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
+        when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt
index 4155318..d2f16d7 100644
--- a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt
@@ -21,8 +21,6 @@
 import android.database.MatrixCursor
 import android.net.Uri
 import android.provider.Telephony
-import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -40,19 +38,15 @@
 
     private val contentResolver = mock<ContentResolver>()
 
-    private val mockSubscriptionInfo = mock<SubscriptionInfo> {
-        on { mccString } doReturn MCC
-        on { mncString } doReturn MNC
-    }
+    private val mockTelephonyManager =
+        mock<TelephonyManager> { on { createForSubscriptionId(SUB_ID) } doReturn mock }
 
-    private val mockSubscriptionManager = mock<SubscriptionManager> {
-        on { getActiveSubscriptionInfo(SUB_ID) } doReturn mockSubscriptionInfo
-    }
+    private val context: Context =
+        spy(ApplicationProvider.getApplicationContext()) {
+            on { contentResolver } doReturn contentResolver
+            on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+        }
 
-    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
-        on { contentResolver } doReturn contentResolver
-        on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager
-    }
     private val uri = mock<Uri> {}
 
     @Test
@@ -91,9 +85,7 @@
 
     @Test
     fun getApnIdMap_knownCarrierId() {
-        mockSubscriptionInfo.stub {
-            on { carrierId } doReturn CARRIER_ID
-        }
+        mockTelephonyManager.stub { on { simSpecificCarrierId } doReturn CARRIER_ID }
 
         val idMap = context.getApnIdMap(SUB_ID)
 
@@ -102,19 +94,19 @@
 
     @Test
     fun getApnIdMap_unknownCarrierId() {
-        mockSubscriptionInfo.stub {
-            on { carrierId } doReturn TelephonyManager.UNKNOWN_CARRIER_ID
+        mockTelephonyManager.stub {
+            on { simSpecificCarrierId } doReturn TelephonyManager.UNKNOWN_CARRIER_ID
+            on { simOperator } doReturn SIM_OPERATOR
         }
 
         val idMap = context.getApnIdMap(SUB_ID)
 
-        assertThat(idMap).containsExactly(Telephony.Carriers.NUMERIC, MCC + MNC)
+        assertThat(idMap).containsExactly(Telephony.Carriers.NUMERIC, SIM_OPERATOR)
     }
 
     private companion object {
         const val SUB_ID = 2
         const val CARRIER_ID = 10
-        const val MCC = "310"
-        const val MNC = "101"
+        const val SIM_OPERATOR = "310101"
     }
 }
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
index f75c14a..5dbc534 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
@@ -190,6 +190,32 @@
     }
 
     @Test
+    fun isSubscriptionVisibleFlow_available_returnTrue() = runBlocking {
+        mockSubscriptionManager.stub {
+            on { getAvailableSubscriptionInfoList() } doReturn
+                listOf(SubscriptionInfo.Builder().apply { setId(SUB_ID_IN_SLOT_0) }.build())
+        }
+
+        val isVisible =
+            repository.isSubscriptionVisibleFlow(SUB_ID_IN_SLOT_0).firstWithTimeoutOrNull()
+
+        assertThat(isVisible).isTrue()
+    }
+
+    @Test
+    fun isSubscriptionVisibleFlow_unavailable_returnFalse() = runBlocking {
+        mockSubscriptionManager.stub {
+            on { getAvailableSubscriptionInfoList() } doReturn
+                listOf(SubscriptionInfo.Builder().apply { setId(SUB_ID_IN_SLOT_0) }.build())
+        }
+
+        val isVisible =
+            repository.isSubscriptionVisibleFlow(SUB_ID_IN_SLOT_1).firstWithTimeoutOrNull()
+
+        assertThat(isVisible).isFalse()
+    }
+
+    @Test
     fun phoneNumberFlow() = runBlocking {
         mockSubscriptionManager.stub {
             on { getPhoneNumber(SUB_ID_IN_SLOT_1) } doReturn NUMBER_1