1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.network; 18 19 import android.app.FragmentManager; 20 import android.app.PendingIntent; 21 import android.telephony.SubscriptionInfo; 22 import android.telephony.SubscriptionManager; 23 import android.telephony.UiccCardInfo; 24 import android.telephony.UiccPortInfo; 25 import android.telephony.UiccSlotInfo; 26 import android.telephony.UiccSlotMapping; 27 import android.telephony.euicc.EuiccManager; 28 import android.util.Log; 29 30 import com.android.settings.SidecarFragment; 31 import com.android.settings.network.telephony.EuiccOperationSidecar; 32 33 import java.util.Collection; 34 import java.util.Comparator; 35 import java.util.List; 36 import java.util.stream.Collectors; 37 38 /** A headless fragment encapsulating long-running eSIM enabling/disabling operations. */ 39 public class SwitchToEuiccSubscriptionSidecar extends EuiccOperationSidecar { 40 private static final String TAG = "SwitchToEuiccSidecar"; 41 private static final String ACTION_SWITCH_TO_SUBSCRIPTION = 42 "com.android.settings.network.SWITCH_TO_SUBSCRIPTION"; 43 44 private PendingIntent mCallbackIntent; 45 private int mSubId; 46 private int mPort; 47 private SubscriptionInfo mRemovedSubInfo; 48 private boolean mIsDuringSimSlotMapping; 49 private List<SubscriptionInfo> mActiveSubInfos; 50 51 /** Returns a SwitchToEuiccSubscriptionSidecar sidecar instance. */ get(FragmentManager fm)52 public static SwitchToEuiccSubscriptionSidecar get(FragmentManager fm) { 53 return SidecarFragment.get( 54 fm, TAG, SwitchToEuiccSubscriptionSidecar.class, null /* args */); 55 } 56 57 @Override getReceiverAction()58 public String getReceiverAction() { 59 return ACTION_SWITCH_TO_SUBSCRIPTION; 60 } 61 62 /** Returns the pendingIntent of the eSIM operations. */ getCallbackIntent()63 public PendingIntent getCallbackIntent() { 64 return mCallbackIntent; 65 } 66 67 @Override onStateChange(SidecarFragment fragment)68 public void onStateChange(SidecarFragment fragment) { 69 if (fragment == mSwitchSlotSidecar) { 70 onSwitchSlotSidecarStateChange(); 71 } else { 72 Log.wtf(TAG, "Received state change from a sidecar not expected."); 73 } 74 } 75 76 /** 77 * Starts calling EuiccManager#switchToSubscription to enable/disable the eSIM profile. 78 * 79 * @param subscriptionId the esim's subscriptionId. 80 * @param port the esim's portId. If user wants to inactivate esim, then user must to assign 81 * the corresponding port. If user wants to activate esim, then the port can be 82 * {@link UiccSlotUtil#INVALID_PORT_ID}. When it is 83 * {@link UiccSlotUtil#INVALID_PORT_ID}, the system will reassign a corresponding 84 * port id. 85 * @param removedSubInfo if the all of slots have sims, it should remove the one of active sim. 86 * If the removedSubInfo is null, then use the default value. 87 * The default value is the esim slot and portId 0. 88 */ run(int subscriptionId, int port, SubscriptionInfo removedSubInfo)89 public void run(int subscriptionId, int port, SubscriptionInfo removedSubInfo) { 90 setState(State.RUNNING, Substate.UNUSED); 91 mCallbackIntent = createCallbackIntent(); 92 mSubId = subscriptionId; 93 94 int targetSlot = getTargetSlot(); 95 if (targetSlot < 0) { 96 Log.d(TAG, "There is no esim, the TargetSlot is " + targetSlot); 97 setState(State.ERROR, Substate.UNUSED); 98 return; 99 } 100 101 SubscriptionManager subscriptionManager = getContext().getSystemService( 102 SubscriptionManager.class).createForAllUserProfiles(); 103 mActiveSubInfos = SubscriptionUtil.getActiveSubscriptions(subscriptionManager); 104 105 // To check whether the esim slot's port is active. If yes, skip setSlotMapping. If no, 106 // set this slot+port into setSimSlotMapping. 107 mPort = (port < 0) ? getTargetPortId(targetSlot, removedSubInfo) : port; 108 mRemovedSubInfo = removedSubInfo; 109 Log.d(TAG, 110 String.format("Set esim into the SubId%d Physical Slot%d:Port%d", 111 mSubId, targetSlot, mPort)); 112 if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 113 // If the subId is INVALID_SUBSCRIPTION_ID, disable the esim (the default esim slot 114 // which is selected by the framework). 115 switchToSubscription(); 116 } else if ((mTelephonyManager.isMultiSimEnabled() && removedSubInfo != null 117 && removedSubInfo.isEmbedded()) 118 || isEsimEnabledAtTargetSlotPort(targetSlot, mPort)) { 119 // Case 1: In DSDS mode+MEP, if the replaced esim is active, then the replaced esim 120 // should be disabled before changing SimSlotMapping process. 121 // 122 // Case 2: If the user enables the esim A on the target slot:port which is active 123 // and there is an active esim B on target slot:port, then the settings disables the 124 // esim B before the settings enables the esim A on the target slot:port. 125 // 126 // Step: 127 // 1) Disables the replaced esim. 128 // 2) Switches the SimSlotMapping if the target slot:port is not active. 129 // 3) Enables the target esim. 130 // Note: Use INVALID_SUBSCRIPTION_ID to disable the esim profile. 131 Log.d(TAG, "Disable the enabled esim before the settings enables the target esim"); 132 mIsDuringSimSlotMapping = true; 133 mEuiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, mPort, 134 mCallbackIntent); 135 } else { 136 mSwitchSlotSidecar.runSwitchToEuiccSlot(targetSlot, mPort, removedSubInfo); 137 } 138 } 139 getTargetPortId(int physicalEsimSlotIndex, SubscriptionInfo removedSubInfo)140 private int getTargetPortId(int physicalEsimSlotIndex, SubscriptionInfo removedSubInfo) { 141 if (!isMultipleEnabledProfilesSupported(physicalEsimSlotIndex)) { 142 Log.d(TAG, "The slotId" + physicalEsimSlotIndex + " is no MEP, port is 0"); 143 return 0; 144 } 145 146 Collection<UiccSlotMapping> uiccSlotMappings = mTelephonyManager.getSimSlotMapping(); 147 Log.d(TAG, "The UiccSlotMapping: " + uiccSlotMappings); 148 if (!mTelephonyManager.isMultiSimEnabled()) { 149 // In the 'SS mode' 150 // If there is the esim slot is active, the port is from the current esim slot. 151 // If there is no esim slot in device, then the esim's port is 0. 152 Log.d(TAG, "In SS mode, to find the active esim slot's port." 153 + "If no active esim slot, the port is 0"); 154 return uiccSlotMappings.stream() 155 .filter(i -> i.getPhysicalSlotIndex() == physicalEsimSlotIndex) 156 .mapToInt(i -> i.getPortIndex()) 157 .findFirst().orElse(0); 158 } 159 160 // In the 'DSDS+MEP', if the removedSubInfo is esim, then the port is 161 // removedSubInfo's port. 162 if (removedSubInfo != null && removedSubInfo.isEmbedded()) { 163 return removedSubInfo.getPortIndex(); 164 } 165 166 // In DSDS+MEP mode, the removedSubInfo is psim or is null, it means this esim needs 167 // a new corresponding port in the esim slot. 168 // For example: 169 // 1) If there is no enabled esim and the user add new esim. This new esim's port is 170 // active esim slot's port. 171 // 2) If there is one enabled esim in port0 and the user add new esim. This new esim's 172 // port is 1. 173 // 3) If there is one enabled esim in port1 and the user add new esim. This new esim's 174 // port is 0. 175 176 int port = 0; 177 if(mActiveSubInfos == null){ 178 Log.d(TAG, "mActiveSubInfos is null."); 179 return port; 180 } 181 List<SubscriptionInfo> activeEsimSubInfos = 182 mActiveSubInfos.stream() 183 .filter(i -> i.isEmbedded()) 184 .sorted(Comparator.comparingInt(SubscriptionInfo::getPortIndex)) 185 .collect(Collectors.toList()); 186 187 // In DSDS+MEP mode, if there is the active esim slot and no active esim at that slot, 188 // then using this active esim slot's port. 189 // If there is no esim slot in device, then the esim's port is 0. 190 if (activeEsimSubInfos.isEmpty()) { 191 Log.d(TAG, "In DSDS+MEP mode, no active esim. return the active esim slot's port." 192 + "If no active esim slot, the port is 0"); 193 return uiccSlotMappings.stream() 194 .filter(i -> i.getPhysicalSlotIndex() == physicalEsimSlotIndex) 195 .mapToInt(i -> i.getPortIndex()) 196 .sorted() 197 .findFirst().orElse(0); 198 } 199 200 for (SubscriptionInfo subscriptionInfo : activeEsimSubInfos) { 201 if (subscriptionInfo.getPortIndex() == port) { 202 port++; 203 } 204 } 205 return port; 206 } 207 getTargetSlot()208 private int getTargetSlot() { 209 return UiccSlotUtil.getEsimSlotId(getContext(), mSubId); 210 } 211 isEsimEnabledAtTargetSlotPort(int physicalSlotIndex, int portIndex)212 private boolean isEsimEnabledAtTargetSlotPort(int physicalSlotIndex, int portIndex) { 213 int logicalSlotId = getLogicalSlotIndex(physicalSlotIndex, portIndex); 214 if (logicalSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { 215 return false; 216 } 217 return mActiveSubInfos != null 218 && mActiveSubInfos.stream() 219 .anyMatch(i -> i.isEmbedded() && i.getSimSlotIndex() == logicalSlotId); 220 } 221 getLogicalSlotIndex(int physicalSlotIndex, int portIndex)222 private int getLogicalSlotIndex(int physicalSlotIndex, int portIndex) { 223 UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo(); 224 if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length 225 && slotInfos[physicalSlotIndex] != null) { 226 for (UiccPortInfo portInfo : slotInfos[physicalSlotIndex].getPorts()) { 227 if (portInfo.getPortIndex() == portIndex) { 228 return portInfo.getLogicalSlotIndex(); 229 } 230 } 231 } 232 233 return SubscriptionManager.INVALID_SIM_SLOT_INDEX; 234 } 235 onSwitchSlotSidecarStateChange()236 private void onSwitchSlotSidecarStateChange() { 237 switch (mSwitchSlotSidecar.getState()) { 238 case State.SUCCESS: 239 mSwitchSlotSidecar.reset(); 240 Log.i(TAG, "Successfully SimSlotMapping. Start to enable/disable esim"); 241 switchToSubscription(); 242 break; 243 case State.ERROR: 244 mSwitchSlotSidecar.reset(); 245 Log.i(TAG, "Failed to set SimSlotMapping"); 246 setState(State.ERROR, Substate.UNUSED); 247 break; 248 } 249 } 250 isMultipleEnabledProfilesSupported(int physicalEsimSlotIndex)251 private boolean isMultipleEnabledProfilesSupported(int physicalEsimSlotIndex) { 252 List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo(); 253 return cardInfos.stream() 254 .anyMatch(cardInfo -> cardInfo.getPhysicalSlotIndex() == physicalEsimSlotIndex 255 && cardInfo.isMultipleEnabledProfilesSupported()); 256 } 257 switchToSubscription()258 private void switchToSubscription() { 259 // The SimSlotMapping is ready, then to execute activate/inactivate esim. 260 mEuiccManager.switchToSubscription(mSubId, mPort, mCallbackIntent); 261 } 262 263 @Override onActionReceived()264 protected void onActionReceived() { 265 if (getResultCode() == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK 266 && mIsDuringSimSlotMapping) { 267 // Continue to switch the SimSlotMapping, after the esim is disabled. 268 mIsDuringSimSlotMapping = false; 269 mSwitchSlotSidecar.runSwitchToEuiccSlot(getTargetSlot(), mPort, mRemovedSubInfo); 270 } else { 271 super.onActionReceived(); 272 } 273 } 274 } 275