Loading AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -3644,6 +3644,14 @@ </intent-filter>/> </receiver> <receiver android:name=".sim.receivers.SimSlotChangeReceiver" android:exported="true"> <intent-filter> <action android:name="android.telephony.action.SIM_SLOT_STATUS_CHANGED" /> </intent-filter> </receiver> <!-- This is the longest AndroidManifest.xml ever. --> </application> </manifest> src/com/android/settings/sim/receivers/SimSlotChangeHandler.java 0 → 100644 +229 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS 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.sim.receivers; import static android.content.Context.MODE_PRIVATE; import android.content.Context; import android.content.SharedPreferences; import android.os.Looper; import android.provider.Settings; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccSlotInfo; import android.util.Log; import com.android.settings.network.SubscriptionUtil; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nullable; /** Perform actions after a slot change event is triggered. */ public class SimSlotChangeHandler { private static final String TAG = "SimSlotChangeHandler"; private static final String EUICC_PREFS = "euicc_prefs"; private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state"; private static volatile SimSlotChangeHandler sSlotChangeHandler; /** Returns a SIM slot change handler singleton. */ public static SimSlotChangeHandler get() { if (sSlotChangeHandler == null) { synchronized (SimSlotChangeHandler.class) { if (sSlotChangeHandler == null) { sSlotChangeHandler = new SimSlotChangeHandler(); } } } return sSlotChangeHandler; } private SubscriptionManager mSubMgr; private TelephonyManager mTelMgr; private Context mContext; void onSlotsStatusChange(Context context) { init(context); if (Looper.myLooper() == Looper.getMainLooper()) { throw new IllegalStateException("Cannot be called from main thread."); } if (mTelMgr.getActiveModemCount() > 1) { Log.i(TAG, "The device is already in DSDS mode. Do nothing."); return; } UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo(); if (removableSlotInfo == null) { Log.e(TAG, "Unable to find the removable slot. Do nothing."); return; } int lastRemovableSlotState = getLastRemovableSimSlotState(mContext); int currentRemovableSlotState = removableSlotInfo.getCardStateInfo(); // Sets the current removable slot state. setRemovableSimSlotState(mContext, currentRemovableSlotState); if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT) { handleSimInsert(removableSlotInfo); return; } if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT) { handleSimRemove(removableSlotInfo); return; } Log.i(TAG, "Do nothing on slot status changes."); } private void init(Context context) { mSubMgr = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mTelMgr = context.getSystemService(TelephonyManager.class); mContext = context; } private void handleSimInsert(UiccSlotInfo removableSlotInfo) { Log.i(TAG, "Detect SIM inserted."); if (!isSuwFinished(mContext)) { // TODO(b/170508680): Store the action and handle it after SUW is finished. Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished"); return; } if (removableSlotInfo.getIsActive()) { Log.i(TAG, "The removable slot is already active. Do nothing."); return; } if (!hasActiveEsimSubscription()) { if (mTelMgr.isMultiSimEnabled()) { Log.i(TAG, "Enabled profile exists. DSDS condition satisfied."); // TODO(b/170508680): Display DSDS dialog to ask users whether to enable DSDS. } else { Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied."); // TODO(b/170508680): Display Choose a number to use screen for subscription // selection. } return; } Log.i( TAG, "No enabled eSIM profile. Ready to switch to removable slot and show" + " notification."); // TODO(b/170508680): Switch the slot to the removebale slot and show the notification. } private void handleSimRemove(UiccSlotInfo removableSlotInfo) { Log.i(TAG, "Detect SIM removed."); if (!isSuwFinished(mContext)) { // TODO(b/170508680): Store the action and handle it after SUW is finished. Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished"); return; } List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions(); if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getIsActive()) { Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing."); return; } // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that // profile. if (groupedEmbeddedSubscriptions.size() == 1) { Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch."); // TODO(b/170508680): Display a dialog to ask users to switch. return; } // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose // the number they want to use. Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use."); // TODO(b/170508680): Display a dialog to ask user which SIM to switch. } private int getLastRemovableSimSlotState(Context context) { final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT); } private void setRemovableSimSlotState(Context context, int state) { final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply(); } @Nullable private UiccSlotInfo getRemovableUiccSlotInfo() { UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo(); if (slotInfos == null) { Log.e(TAG, "slotInfos is null. Unable to get slot infos."); return null; } for (UiccSlotInfo slotInfo : slotInfos) { if (slotInfo != null && slotInfo.isRemovable()) { return slotInfo; } } return null; } private static boolean isSuwFinished(Context context) { try { // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed. return Settings.Global.getInt( context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED) == 1; } catch (Settings.SettingNotFoundException e) { Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e); return false; } } private boolean hasActiveEsimSubscription() { List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr); return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded); } private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() { List<SubscriptionInfo> groupedSubscriptions = SubscriptionUtil.getSelectableSubscriptionInfoList(mContext); if (groupedSubscriptions == null) { return ImmutableList.of(); } return ImmutableList.copyOf( groupedSubscriptions.stream() .filter(sub -> sub.isEmbedded()) .collect(Collectors.toList())); } private SimSlotChangeHandler() {} } src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS 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.sim.receivers; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import android.telephony.UiccCardInfo; import android.telephony.UiccSlotInfo; import android.telephony.euicc.EuiccManager; import android.text.TextUtils; import android.util.Log; import androidx.annotation.Nullable; import com.android.settingslib.utils.ThreadUtils; import java.util.List; /** The receiver when the slot status changes. */ public class SimSlotChangeReceiver extends BroadcastReceiver { private static final String TAG = "SlotChangeReceiver"; private final SimSlotChangeHandler mSlotChangeHandler = SimSlotChangeHandler.get(); private final Object mLock = new Object(); @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (!TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED.equals(action)) { Log.e(TAG, "Ignore slot changes due to unexpected action: " + action); return; } ThreadUtils.postOnBackgroundThread( () -> { synchronized (mLock) { if (!shouldHandleSlotChange(context)) { return; } mSlotChangeHandler.onSlotsStatusChange(context); } }); } // Checks whether the slot event should be handled. private boolean shouldHandleSlotChange(Context context) { final EuiccManager euiccManager = context.getSystemService(EuiccManager.class); if (euiccManager == null || !euiccManager.isEnabled()) { Log.i(TAG, "Ignore slot changes because EuiccManager is disabled."); return false; } if (euiccManager.getOtaStatus() == EuiccManager.EUICC_OTA_IN_PROGRESS) { Log.i(TAG, "Ignore slot changes because eSIM OTA is in progress."); return false; } if (!isSimSlotStateValid(context)) { Log.i(TAG, "Ignore slot changes because SIM states are not valid."); return false; } return true; } // Checks whether the SIM slot state is valid for slot change event. private boolean isSimSlotStateValid(Context context) { final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo(); if (slotInfos == null) { Log.e(TAG, "slotInfos is null. Unable to get slot infos."); return false; } boolean isAllCardStringsEmpty = true; for (int i = 0; i < slotInfos.length; i++) { UiccSlotInfo slotInfo = slotInfos[i]; if (slotInfo == null) { return false; } // After pSIM is inserted, there might be a short period that the status of both slots // are not accurate. We drop the event if any of sim presence state is ERROR or // RESTRICTED. if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ERROR || slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) { Log.i(TAG, "The SIM state is in an error. Drop the event. SIM info: " + slotInfo); return false; } UiccCardInfo cardInfo = findUiccCardInfoBySlot(telMgr, i); if (cardInfo == null) { continue; } if (!TextUtils.isEmpty(slotInfo.getCardId()) || !TextUtils.isEmpty(cardInfo.getIccId())) { isAllCardStringsEmpty = false; } } // We also drop the event if both the card strings are empty, which usually means it's // between SIM slots switch the slot status is not stable at this moment. if (isAllCardStringsEmpty) { Log.i(TAG, "All UICC card strings are empty. Drop this event."); return false; } return true; } @Nullable private UiccCardInfo findUiccCardInfoBySlot(TelephonyManager telMgr, int physicalSlotIndex) { List<UiccCardInfo> cardInfos = telMgr.getUiccCardsInfo(); if (cardInfos == null) { return null; } return cardInfos.stream() .filter(info -> info.getSlotIndex() == physicalSlotIndex) .findFirst() .orElse(null); } } Loading
AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -3644,6 +3644,14 @@ </intent-filter>/> </receiver> <receiver android:name=".sim.receivers.SimSlotChangeReceiver" android:exported="true"> <intent-filter> <action android:name="android.telephony.action.SIM_SLOT_STATUS_CHANGED" /> </intent-filter> </receiver> <!-- This is the longest AndroidManifest.xml ever. --> </application> </manifest>
src/com/android/settings/sim/receivers/SimSlotChangeHandler.java 0 → 100644 +229 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS 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.sim.receivers; import static android.content.Context.MODE_PRIVATE; import android.content.Context; import android.content.SharedPreferences; import android.os.Looper; import android.provider.Settings; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccSlotInfo; import android.util.Log; import com.android.settings.network.SubscriptionUtil; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nullable; /** Perform actions after a slot change event is triggered. */ public class SimSlotChangeHandler { private static final String TAG = "SimSlotChangeHandler"; private static final String EUICC_PREFS = "euicc_prefs"; private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state"; private static volatile SimSlotChangeHandler sSlotChangeHandler; /** Returns a SIM slot change handler singleton. */ public static SimSlotChangeHandler get() { if (sSlotChangeHandler == null) { synchronized (SimSlotChangeHandler.class) { if (sSlotChangeHandler == null) { sSlotChangeHandler = new SimSlotChangeHandler(); } } } return sSlotChangeHandler; } private SubscriptionManager mSubMgr; private TelephonyManager mTelMgr; private Context mContext; void onSlotsStatusChange(Context context) { init(context); if (Looper.myLooper() == Looper.getMainLooper()) { throw new IllegalStateException("Cannot be called from main thread."); } if (mTelMgr.getActiveModemCount() > 1) { Log.i(TAG, "The device is already in DSDS mode. Do nothing."); return; } UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo(); if (removableSlotInfo == null) { Log.e(TAG, "Unable to find the removable slot. Do nothing."); return; } int lastRemovableSlotState = getLastRemovableSimSlotState(mContext); int currentRemovableSlotState = removableSlotInfo.getCardStateInfo(); // Sets the current removable slot state. setRemovableSimSlotState(mContext, currentRemovableSlotState); if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT) { handleSimInsert(removableSlotInfo); return; } if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT) { handleSimRemove(removableSlotInfo); return; } Log.i(TAG, "Do nothing on slot status changes."); } private void init(Context context) { mSubMgr = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mTelMgr = context.getSystemService(TelephonyManager.class); mContext = context; } private void handleSimInsert(UiccSlotInfo removableSlotInfo) { Log.i(TAG, "Detect SIM inserted."); if (!isSuwFinished(mContext)) { // TODO(b/170508680): Store the action and handle it after SUW is finished. Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished"); return; } if (removableSlotInfo.getIsActive()) { Log.i(TAG, "The removable slot is already active. Do nothing."); return; } if (!hasActiveEsimSubscription()) { if (mTelMgr.isMultiSimEnabled()) { Log.i(TAG, "Enabled profile exists. DSDS condition satisfied."); // TODO(b/170508680): Display DSDS dialog to ask users whether to enable DSDS. } else { Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied."); // TODO(b/170508680): Display Choose a number to use screen for subscription // selection. } return; } Log.i( TAG, "No enabled eSIM profile. Ready to switch to removable slot and show" + " notification."); // TODO(b/170508680): Switch the slot to the removebale slot and show the notification. } private void handleSimRemove(UiccSlotInfo removableSlotInfo) { Log.i(TAG, "Detect SIM removed."); if (!isSuwFinished(mContext)) { // TODO(b/170508680): Store the action and handle it after SUW is finished. Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished"); return; } List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions(); if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getIsActive()) { Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing."); return; } // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that // profile. if (groupedEmbeddedSubscriptions.size() == 1) { Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch."); // TODO(b/170508680): Display a dialog to ask users to switch. return; } // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose // the number they want to use. Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use."); // TODO(b/170508680): Display a dialog to ask user which SIM to switch. } private int getLastRemovableSimSlotState(Context context) { final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT); } private void setRemovableSimSlotState(Context context, int state) { final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply(); } @Nullable private UiccSlotInfo getRemovableUiccSlotInfo() { UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo(); if (slotInfos == null) { Log.e(TAG, "slotInfos is null. Unable to get slot infos."); return null; } for (UiccSlotInfo slotInfo : slotInfos) { if (slotInfo != null && slotInfo.isRemovable()) { return slotInfo; } } return null; } private static boolean isSuwFinished(Context context) { try { // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed. return Settings.Global.getInt( context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED) == 1; } catch (Settings.SettingNotFoundException e) { Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e); return false; } } private boolean hasActiveEsimSubscription() { List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr); return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded); } private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() { List<SubscriptionInfo> groupedSubscriptions = SubscriptionUtil.getSelectableSubscriptionInfoList(mContext); if (groupedSubscriptions == null) { return ImmutableList.of(); } return ImmutableList.copyOf( groupedSubscriptions.stream() .filter(sub -> sub.isEmbedded()) .collect(Collectors.toList())); } private SimSlotChangeHandler() {} }
src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS 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.sim.receivers; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import android.telephony.UiccCardInfo; import android.telephony.UiccSlotInfo; import android.telephony.euicc.EuiccManager; import android.text.TextUtils; import android.util.Log; import androidx.annotation.Nullable; import com.android.settingslib.utils.ThreadUtils; import java.util.List; /** The receiver when the slot status changes. */ public class SimSlotChangeReceiver extends BroadcastReceiver { private static final String TAG = "SlotChangeReceiver"; private final SimSlotChangeHandler mSlotChangeHandler = SimSlotChangeHandler.get(); private final Object mLock = new Object(); @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (!TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED.equals(action)) { Log.e(TAG, "Ignore slot changes due to unexpected action: " + action); return; } ThreadUtils.postOnBackgroundThread( () -> { synchronized (mLock) { if (!shouldHandleSlotChange(context)) { return; } mSlotChangeHandler.onSlotsStatusChange(context); } }); } // Checks whether the slot event should be handled. private boolean shouldHandleSlotChange(Context context) { final EuiccManager euiccManager = context.getSystemService(EuiccManager.class); if (euiccManager == null || !euiccManager.isEnabled()) { Log.i(TAG, "Ignore slot changes because EuiccManager is disabled."); return false; } if (euiccManager.getOtaStatus() == EuiccManager.EUICC_OTA_IN_PROGRESS) { Log.i(TAG, "Ignore slot changes because eSIM OTA is in progress."); return false; } if (!isSimSlotStateValid(context)) { Log.i(TAG, "Ignore slot changes because SIM states are not valid."); return false; } return true; } // Checks whether the SIM slot state is valid for slot change event. private boolean isSimSlotStateValid(Context context) { final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo(); if (slotInfos == null) { Log.e(TAG, "slotInfos is null. Unable to get slot infos."); return false; } boolean isAllCardStringsEmpty = true; for (int i = 0; i < slotInfos.length; i++) { UiccSlotInfo slotInfo = slotInfos[i]; if (slotInfo == null) { return false; } // After pSIM is inserted, there might be a short period that the status of both slots // are not accurate. We drop the event if any of sim presence state is ERROR or // RESTRICTED. if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ERROR || slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) { Log.i(TAG, "The SIM state is in an error. Drop the event. SIM info: " + slotInfo); return false; } UiccCardInfo cardInfo = findUiccCardInfoBySlot(telMgr, i); if (cardInfo == null) { continue; } if (!TextUtils.isEmpty(slotInfo.getCardId()) || !TextUtils.isEmpty(cardInfo.getIccId())) { isAllCardStringsEmpty = false; } } // We also drop the event if both the card strings are empty, which usually means it's // between SIM slots switch the slot status is not stable at this moment. if (isAllCardStringsEmpty) { Log.i(TAG, "All UICC card strings are empty. Drop this event."); return false; } return true; } @Nullable private UiccCardInfo findUiccCardInfoBySlot(TelephonyManager telMgr, int physicalSlotIndex) { List<UiccCardInfo> cardInfos = telMgr.getUiccCardsInfo(); if (cardInfos == null) { return null; } return cardInfos.stream() .filter(info -> info.getSlotIndex() == physicalSlotIndex) .findFirst() .orElse(null); } }