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 com.google.common.collect.ImmutableList; 34 35 import java.util.Collection; 36 import java.util.Comparator; 37 import java.util.List; 38 import java.util.stream.Collectors; 39 40 /** A headless fragment encapsulating long-running eSIM enabling/disabling operations. */ 41 public class SwitchToEuiccSubscriptionSidecar extends EuiccOperationSidecar { 42 private static final String TAG = "SwitchToEuiccSidecar"; 43 private static final String ACTION_SWITCH_TO_SUBSCRIPTION = 44 "com.android.settings.network.SWITCH_TO_SUBSCRIPTION"; 45 46 private PendingIntent mCallbackIntent; 47 private int mSubId; 48 private int mPort; 49 private SubscriptionInfo mRemovedSubInfo; 50 private boolean mIsDuringSimSlotMapping; 51 private List<SubscriptionInfo> mActiveSubInfos; 52 53 /** Returns a SwitchToEuiccSubscriptionSidecar sidecar instance. */ get(FragmentManager fm)54 public static SwitchToEuiccSubscriptionSidecar get(FragmentManager fm) { 55 return SidecarFragment.get( 56 fm, TAG, SwitchToEuiccSubscriptionSidecar.class, null /* args */); 57 } 58 59 @Override getReceiverAction()60 public String getReceiverAction() { 61 return ACTION_SWITCH_TO_SUBSCRIPTION; 62 } 63 64 /** Returns the pendingIntent of the eSIM operations. */ getCallbackIntent()65 public PendingIntent getCallbackIntent() { 66 return mCallbackIntent; 67 } 68 69 @Override onStateChange(SidecarFragment fragment)70 public void onStateChange(SidecarFragment fragment) { 71 if (fragment == mSwitchSlotSidecar) { 72 onSwitchSlotSidecarStateChange(); 73 } else { 74 Log.wtf(TAG, "Received state change from a sidecar not expected."); 75 } 76 } 77 78 /** 79 * Starts calling EuiccManager#switchToSubscription to enable/disable the eSIM profile. 80 * 81 * @param subscriptionId the esim's subscriptionId. 82 * @param port the esim's portId. If user wants to inactivate esim, then user must to assign 83 * the corresponding port. If user wants to activate esim, then the port can be 84 * {@link UiccSlotUtil#INVALID_PORT_ID}. When it is 85 * {@link UiccSlotUtil#INVALID_PORT_ID}, the system will reassign a corresponding 86 * port id. 87 * @param removedSubInfo if the all of slots have sims, it should remove the one of active sim. 88 * If the removedSubInfo is null, then use the default value. 89 * The default value is the esim slot and portId 0. 90 */ run(int subscriptionId, int port, SubscriptionInfo removedSubInfo)91 public void run(int subscriptionId, int port, SubscriptionInfo removedSubInfo) { 92 setState(State.RUNNING, Substate.UNUSED); 93 mCallbackIntent = createCallbackIntent(); 94 mSubId = subscriptionId; 95 96 int targetSlot = getTargetSlot(); 97 if (targetSlot < 0) { 98 Log.d(TAG, "There is no esim, the TargetSlot is " + targetSlot); 99 setState(State.ERROR, Substate.UNUSED); 100 return; 101 } 102 103 SubscriptionManager subscriptionManager = getContext().getSystemService( 104 SubscriptionManager.class).createForAllUserProfiles(); 105 mActiveSubInfos = SubscriptionUtil.getActiveSubscriptions(subscriptionManager); 106 107 // To check whether the esim slot's port is active. If yes, skip setSlotMapping. If no, 108 // set this slot+port into setSimSlotMapping. 109 mPort = (port < 0) ? getTargetPortId(targetSlot, removedSubInfo) : port; 110 mRemovedSubInfo = removedSubInfo; 111 Log.d(TAG, 112 String.format("Set esim into the SubId%d Physical Slot%d:Port%d", 113 mSubId, targetSlot, mPort)); 114 if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 115 // If the subId is INVALID_SUBSCRIPTION_ID, disable the esim (the default esim slot 116 // which is selected by the framework). 117 switchToSubscription(); 118 } else if ((mTelephonyManager.isMultiSimEnabled() && removedSubInfo != null 119 && removedSubInfo.isEmbedded()) 120 || isEsimEnabledAtTargetSlotPort(targetSlot, mPort)) { 121 // Case 1: In DSDS mode+MEP, if the replaced esim is active, then the replaced esim 122 // should be disabled before changing SimSlotMapping process. 123 // 124 // Case 2: If the user enables the esim A on the target slot:port which is active 125 // and there is an active esim B on target slot:port, then the settings disables the 126 // esim B before the settings enables the esim A on the target slot:port. 127 // 128 // Step: 129 // 1) Disables the replaced esim. 130 // 2) Switches the SimSlotMapping if the target slot:port is not active. 131 // 3) Enables the target esim. 132 // Note: Use INVALID_SUBSCRIPTION_ID to disable the esim profile. 133 Log.d(TAG, "Disable the enabled esim before the settings enables the target esim"); 134 mIsDuringSimSlotMapping = true; 135 mEuiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, mPort, 136 mCallbackIntent); 137 } else { 138 mSwitchSlotSidecar.runSwitchToEuiccSlot(targetSlot, mPort, removedSubInfo); 139 } 140 } 141 getTargetPortId(int physicalEsimSlotIndex, SubscriptionInfo removedSubInfo)142 private int getTargetPortId(int physicalEsimSlotIndex, SubscriptionInfo removedSubInfo) { 143 if (!isMultipleEnabledProfilesSupported(physicalEsimSlotIndex)) { 144 Log.d(TAG, "The slotId" + physicalEsimSlotIndex + " is no MEP, port is 0"); 145 return 0; 146 } 147 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 Collection<UiccSlotMapping> uiccSlotMappings = mTelephonyManager.getSimSlotMapping(); 153 Log.d(TAG, "In SS mode, the UiccSlotMapping: " + uiccSlotMappings); 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 0. 170 // 2) If there is one enabled esim in port0 and the user add new esim. This new esim's 171 // port is 1. 172 // 3) If there is one enabled esim in port1 and the user add new esim. This new esim's 173 // port is 0. 174 175 int port = 0; 176 if(mActiveSubInfos == null){ 177 Log.d(TAG, "mActiveSubInfos is null."); 178 return port; 179 } 180 List<SubscriptionInfo> activeEsimSubInfos = 181 mActiveSubInfos.stream() 182 .filter(i -> i.isEmbedded()) 183 .sorted(Comparator.comparingInt(SubscriptionInfo::getPortIndex)) 184 .collect(Collectors.toList()); 185 for (SubscriptionInfo subscriptionInfo : activeEsimSubInfos) { 186 if (subscriptionInfo.getPortIndex() == port) { 187 port++; 188 } 189 } 190 return port; 191 } 192 getTargetSlot()193 private int getTargetSlot() { 194 return UiccSlotUtil.getEsimSlotId(getContext(), mSubId); 195 } 196 isEsimEnabledAtTargetSlotPort(int physicalSlotIndex, int portIndex)197 private boolean isEsimEnabledAtTargetSlotPort(int physicalSlotIndex, int portIndex) { 198 int logicalSlotId = getLogicalSlotIndex(physicalSlotIndex, portIndex); 199 if (logicalSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { 200 return false; 201 } 202 return mActiveSubInfos != null 203 && mActiveSubInfos.stream() 204 .anyMatch(i -> i.isEmbedded() && i.getSimSlotIndex() == logicalSlotId); 205 } 206 getLogicalSlotIndex(int physicalSlotIndex, int portIndex)207 private int getLogicalSlotIndex(int physicalSlotIndex, int portIndex) { 208 ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(mTelephonyManager); 209 if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.size() 210 && slotInfos.get(physicalSlotIndex) != null) { 211 for (UiccPortInfo portInfo : slotInfos.get(physicalSlotIndex).getPorts()) { 212 if (portInfo.getPortIndex() == portIndex) { 213 return portInfo.getLogicalSlotIndex(); 214 } 215 } 216 } 217 218 return SubscriptionManager.INVALID_SIM_SLOT_INDEX; 219 } 220 onSwitchSlotSidecarStateChange()221 private void onSwitchSlotSidecarStateChange() { 222 switch (mSwitchSlotSidecar.getState()) { 223 case State.SUCCESS: 224 mSwitchSlotSidecar.reset(); 225 Log.i(TAG, "Successfully SimSlotMapping. Start to enable/disable esim"); 226 switchToSubscription(); 227 break; 228 case State.ERROR: 229 mSwitchSlotSidecar.reset(); 230 Log.i(TAG, "Failed to set SimSlotMapping"); 231 setState(State.ERROR, Substate.UNUSED); 232 break; 233 } 234 } 235 isMultipleEnabledProfilesSupported(int physicalEsimSlotIndex)236 private boolean isMultipleEnabledProfilesSupported(int physicalEsimSlotIndex) { 237 List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo(); 238 return cardInfos.stream() 239 .anyMatch(cardInfo -> cardInfo.getPhysicalSlotIndex() == physicalEsimSlotIndex 240 && cardInfo.isMultipleEnabledProfilesSupported()); 241 } 242 switchToSubscription()243 private void switchToSubscription() { 244 // The SimSlotMapping is ready, then to execute activate/inactivate esim. 245 mEuiccManager.switchToSubscription(mSubId, mPort, mCallbackIntent); 246 } 247 248 @Override onActionReceived()249 protected void onActionReceived() { 250 if (getResultCode() == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK 251 && mIsDuringSimSlotMapping) { 252 // Continue to switch the SimSlotMapping, after the esim is disabled. 253 mIsDuringSimSlotMapping = false; 254 mSwitchSlotSidecar.runSwitchToEuiccSlot(getTargetSlot(), mPort, mRemovedSubInfo); 255 } else { 256 super.onActionReceived(); 257 } 258 } 259 } 260