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