• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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