1 /* 2 * Copyright (C) 2018 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.ons; 18 19 import android.app.PendingIntent; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.os.ParcelUuid; 26 import android.os.PersistableBundle; 27 import android.telephony.CarrierConfigManager; 28 import android.telephony.SubscriptionInfo; 29 import android.telephony.SubscriptionManager; 30 import android.telephony.euicc.EuiccManager; 31 import android.util.Log; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** 39 * @class ONSProfileConfigurator 40 * @brief Helper class to support ONSProfileActivator to read and update profile, operator and CBRS 41 * configurations. 42 */ 43 public class ONSProfileConfigurator { 44 45 private static final String TAG = ONSProfileConfigurator.class.getName(); 46 @VisibleForTesting protected static final String PARAM_SUB_ID = "SUB_ID"; 47 @VisibleForTesting protected static final String PARAM_REQUEST_TYPE = "REQUEST_TYPE"; 48 @VisibleForTesting protected static final int REQUEST_CODE_ACTIVATE_SUB = 1; 49 @VisibleForTesting protected static final int REQUEST_CODE_DELETE_SUB = 2; 50 @VisibleForTesting 51 protected static final String ACTION_ONS_ESIM_CONFIG = "com.android.ons.action.ESIM_CONFIG"; 52 53 private final Context mContext; 54 private final SubscriptionManager mSubscriptionManager; 55 private final CarrierConfigManager mCarrierConfigManager; 56 private final EuiccManager mEuiccManager; 57 private ONSProfConfigListener mONSProfConfigListener = null; 58 private final Handler mHandler; 59 ONSProfileConfigurator(Context context, SubscriptionManager subscriptionManager, CarrierConfigManager carrierConfigManager, EuiccManager euiccManager, ONSProfConfigListener listener)60 public ONSProfileConfigurator(Context context, SubscriptionManager subscriptionManager, 61 CarrierConfigManager carrierConfigManager, 62 EuiccManager euiccManager, ONSProfConfigListener listener) { 63 mContext = context; 64 mSubscriptionManager = subscriptionManager; 65 mCarrierConfigManager = carrierConfigManager; 66 mEuiccManager = euiccManager; 67 mONSProfConfigListener = listener; 68 69 mHandler = new Handler(Looper.myLooper()) { 70 @Override 71 public void handleMessage(Message msg) { 72 super.handleMessage(msg); 73 callbackMsgHandler(msg); 74 } 75 }; 76 } 77 78 /** 79 * Callback to receive result for subscription activate request and process the same. 80 * 81 * @param intent 82 * @param resultCode 83 */ onCallbackIntentReceived(Intent intent, int resultCode)84 public void onCallbackIntentReceived(Intent intent, int resultCode) { 85 Message msg = new Message(); 86 msg.obj = intent; 87 msg.arg1 = resultCode; 88 mHandler.sendMessage(msg); 89 } 90 91 @VisibleForTesting callbackMsgHandler(Message msg)92 protected void callbackMsgHandler(Message msg) { 93 Intent intent = (Intent) msg.obj; 94 int resultCode = msg.arg1; 95 96 int reqCode = intent.getIntExtra(PARAM_REQUEST_TYPE, 0); 97 switch (reqCode) { 98 case REQUEST_CODE_ACTIVATE_SUB: { 99 /*int subId = intent.getIntExtra(ACTIVATE_SUB_ID, 0);*/ 100 Log.d(TAG, "Opportunistic subscription activated successfully. SubId:" 101 + intent.getIntExtra(PARAM_SUB_ID, 0)); 102 Log.d(TAG, "Detailed result code: " + intent.getIntExtra( 103 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0)); 104 } 105 break; 106 case REQUEST_CODE_DELETE_SUB: { 107 if (resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { 108 Log.e(TAG, "Error removing euicc opportunistic profile." 109 + "Detailed error code = " + intent.getIntExtra( 110 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0)); 111 } else if (mONSProfConfigListener != null) { 112 int subId = intent.getIntExtra(PARAM_SUB_ID, 0); 113 mONSProfConfigListener.onOppSubscriptionDeleted(subId); 114 Log.d(TAG, "Opportunistic subscription deleted successfully. Id:" + subId); 115 } 116 } 117 break; 118 } 119 }; 120 121 /** 122 * Adds downloaded subscription to the group, activates and enables opportunistic data. 123 * 124 * @param opportunisticESIM 125 * @param groupUuid 126 */ 127 @VisibleForTesting groupWithPSIMAndSetOpportunistic( SubscriptionInfo opportunisticESIM, ParcelUuid groupUuid)128 protected void groupWithPSIMAndSetOpportunistic( 129 SubscriptionInfo opportunisticESIM, ParcelUuid groupUuid) { 130 if (groupUuid != null && groupUuid.equals(opportunisticESIM.getGroupUuid())) { 131 Log.d(TAG, "opportunistc eSIM and CBRS pSIM already grouped"); 132 } else { 133 Log.d(TAG, "Grouping opportunistc eSIM and CBRS pSIM"); 134 ArrayList<Integer> subList = new ArrayList<>(); 135 subList.add(opportunisticESIM.getSubscriptionId()); 136 try { 137 mSubscriptionManager.addSubscriptionsIntoGroup(subList, groupUuid); 138 } catch (RuntimeException re) { 139 // Telephony not found 140 Log.e(TAG, "Subscription group add failed.", re); 141 } 142 } 143 144 if (!opportunisticESIM.isOpportunistic()) { 145 Log.d(TAG, "set Opportunistic to TRUE"); 146 mSubscriptionManager.setOpportunistic(true, 147 opportunisticESIM.getSubscriptionId()); 148 } 149 //activateSubscription(opportunisticESIM);// -> activate after download flag is passed as 150 //true in download request so no need of explicit activation. 151 } 152 153 /** 154 * Activates provided subscription. Result is received in method onCallbackIntentReceived. 155 */ activateSubscription(int subId)156 public void activateSubscription(int subId) { 157 Intent intent = new Intent(mContext, ONSProfileResultReceiver.class); 158 intent.setAction(ACTION_ONS_ESIM_CONFIG); 159 intent.putExtra(PARAM_REQUEST_TYPE, REQUEST_CODE_ACTIVATE_SUB); 160 intent.putExtra(PARAM_SUB_ID, subId); 161 PendingIntent callbackIntent = PendingIntent.getBroadcast(mContext, 162 REQUEST_CODE_ACTIVATE_SUB, intent, PendingIntent.FLAG_IMMUTABLE); 163 Log.d(TAG, "Activate oppSub request sent to SubManager"); 164 165 List<SubscriptionInfo> subInfoList = mSubscriptionManager 166 .getAvailableSubscriptionInfoList(); 167 for (SubscriptionInfo subInfo : subInfoList) { 168 if (subId == subInfo.getSubscriptionId()) { 169 EuiccManager euiccManager = mEuiccManager.createForCardId(subInfo.getCardId()); 170 // eUICC and the platform will internally resolve a port. If there is no available 171 // port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned 172 // in the callback intent to prompt the user to disable an already-active 173 // subscription. However, ONS will not show any prompt to the user and silently 174 // fails to activate the subscription. ONS will try to provision again when 175 // carrier configuration change event is received. 176 euiccManager.switchToSubscription(subId, callbackIntent); 177 break; 178 } 179 } 180 } 181 182 /** 183 * Deletes inactive opportunistic subscriptions irrespective of the CBRS operator. 184 * Called when sufficient memory is not available before downloading new profile. 185 */ deleteInactiveOpportunisticSubscriptions(int pSIMId)186 public boolean deleteInactiveOpportunisticSubscriptions(int pSIMId) { 187 Log.d(TAG, "deleteInactiveOpportunisticSubscriptions"); 188 189 List<SubscriptionInfo> subList = mSubscriptionManager.getOpportunisticSubscriptions(); 190 if (subList == null || subList.size() <= 0) { 191 return false; 192 } 193 194 for (SubscriptionInfo subInfo : subList) { 195 int subId = subInfo.getSubscriptionId(); 196 if (!mSubscriptionManager.isActiveSubscriptionId(subId)) { 197 deleteSubscription(subId); 198 return true; 199 } 200 } 201 202 return false; 203 } 204 205 /** 206 * Returns previously downloaded opportunistic eSIM associated with pSIM CBRS operator. 207 * Helpful to cleanup before downloading new opportunistic eSIM from the same CBRS operator. 208 * 209 * @return true - If an eSIM is found. 210 * false - If no eSIM is found. 211 */ getOpportunisticSubIdsofPSIMOperator(int pSIMSubId)212 ArrayList<Integer> getOpportunisticSubIdsofPSIMOperator(int pSIMSubId) { 213 Log.d(TAG, "getOpportunisticSubIdsofPSIMOperator"); 214 ArrayList<Integer> opportunisticSubIds = new ArrayList<Integer>(); 215 //1.Get the list of all opportunistic carrier-ids of newly inserted pSIM from carrier config 216 PersistableBundle config = mCarrierConfigManager.getConfigForSubId(pSIMSubId); 217 int[] oppCarrierIdArr = config.getIntArray( 218 CarrierConfigManager.KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY); 219 if (oppCarrierIdArr == null || oppCarrierIdArr.length <= 0) { 220 return null; 221 } 222 223 //2. Get list of all subscriptions 224 List<SubscriptionInfo> oppSubList = mSubscriptionManager.getAvailableSubscriptionInfoList(); 225 for (SubscriptionInfo subInfo : oppSubList) { 226 for (int oppCarrierId : oppCarrierIdArr) { 227 //Carrier-id of opportunistic eSIM matches with one of thecarrier-ids in carrier 228 // config of pSIM 229 if (subInfo.isEmbedded() && oppCarrierId == subInfo 230 .getCarrierId()) { 231 //3.if carrier-id of eSIM matches with one of the pSIM opportunistic carrier-ids 232 //and eSIM's pSIM carrier-id matches with new pSIM then delete the subscription 233 opportunisticSubIds.add(subInfo.getSubscriptionId()); 234 } 235 } 236 } 237 238 return opportunisticSubIds; 239 } 240 241 /** 242 * Sends delete request to the eUICC manager to delete a given subscription. 243 * @param subId 244 */ deleteSubscription(int subId)245 public void deleteSubscription(int subId) { 246 Log.d(TAG, "deleting subscription. SubId: " + subId); 247 Intent intent = new Intent(mContext, ONSProfileResultReceiver.class); 248 intent.setAction(ACTION_ONS_ESIM_CONFIG); 249 intent.putExtra(PARAM_REQUEST_TYPE, REQUEST_CODE_DELETE_SUB); 250 intent.putExtra(PARAM_SUB_ID, subId); 251 PendingIntent callbackIntent = PendingIntent.getBroadcast(mContext, 252 REQUEST_CODE_DELETE_SUB, intent, PendingIntent.FLAG_MUTABLE); 253 254 List<SubscriptionInfo> subInfoList = mSubscriptionManager 255 .getAvailableSubscriptionInfoList(); 256 for (SubscriptionInfo subInfo : subInfoList) { 257 if (subId == subInfo.getSubscriptionId()) { 258 EuiccManager euiccManager = mEuiccManager.createForCardId(subInfo.getCardId()); 259 euiccManager.deleteSubscription(subId, callbackIntent); 260 break; 261 } 262 } 263 } 264 265 /** 266 * Creates Subscription Group for PSIM if it doesn't exist or returns existing group-id. 267 */ getPSIMGroupId(SubscriptionInfo primaryCBRSSubInfo)268 public ParcelUuid getPSIMGroupId(SubscriptionInfo primaryCBRSSubInfo) { 269 ParcelUuid groupId = primaryCBRSSubInfo.getGroupUuid(); 270 if (groupId != null) { 271 return groupId; 272 } 273 274 Log.d(TAG, "Creating Group for Primary SIM"); 275 List<Integer> pSubList = new ArrayList<>(); 276 pSubList.add(primaryCBRSSubInfo.getSubscriptionId()); 277 ParcelUuid puid = null; 278 try { 279 puid = mSubscriptionManager.createSubscriptionGroup(pSubList); 280 } catch (RuntimeException re) { 281 // Telephony not found 282 Log.e(TAG, "Subscription group creation failed.", re); 283 } 284 return puid; 285 } 286 287 /** 288 * Searches for opportunistic profile in all available subscriptions using carrier-ids 289 * from carrier configuration and returns opportunistic subscription. 290 */ findOpportunisticSubscription(int pSIMId)291 public SubscriptionInfo findOpportunisticSubscription(int pSIMId) { 292 Log.d(TAG, "findOpportunisticSubscription. PSIM Id : " + pSIMId); 293 294 //Get the list of active subscriptions 295 List<SubscriptionInfo> availSubInfoList = mSubscriptionManager 296 .getAvailableSubscriptionInfoList(); 297 if (availSubInfoList == null) { 298 Log.e(TAG, "getAvailableSubscriptionInfoList returned null"); 299 return null; 300 } 301 Log.d(TAG, "Available subscriptions: " + availSubInfoList.size()); 302 303 //Get the list of opportunistic carrier-ids list from carrier config. 304 PersistableBundle config = mCarrierConfigManager.getConfigForSubId(pSIMId); 305 int[] oppCarrierIdArr = config.getIntArray( 306 CarrierConfigManager.KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY); 307 if (oppCarrierIdArr == null || oppCarrierIdArr.length <= 0) { 308 Log.e(TAG, "Opportunistic carrier-ids list is empty in carrier config"); 309 return null; 310 } 311 312 SubscriptionInfo subscriptionInfo = mSubscriptionManager.getActiveSubscriptionInfo(pSIMId); 313 if (subscriptionInfo == null) { 314 Log.e(TAG, "getActiveSubscriptionInfo returned null for: " + pSIMId); 315 return null; 316 } 317 ParcelUuid pSIMSubGroupId = subscriptionInfo.getGroupUuid(); 318 for (SubscriptionInfo subInfo : availSubInfoList) { 319 if (subInfo.getSubscriptionId() != pSIMId) { 320 for (int carrId : oppCarrierIdArr) { 321 if (subInfo.isEmbedded() && carrId == subInfo.getCarrierId()) { 322 //An active eSIM whose carrier-id is listed as opportunistic carrier in 323 // carrier config is newly downloaded opportunistic eSIM. 324 325 ParcelUuid oppSubGroupId = subInfo.getGroupUuid(); 326 if (oppSubGroupId == null /*Immediately after opp eSIM is downloaded case*/ 327 || oppSubGroupId.equals(pSIMSubGroupId) /*Already downloaded and 328 grouped case.*/) { 329 Log.d(TAG, "Opp subscription:" + subInfo.getSubscriptionId()); 330 return subInfo; 331 } 332 } 333 } 334 } 335 } 336 337 return null; 338 } 339 340 /** 341 * Listener interface to notify delete subscription operation. 342 */ 343 public interface ONSProfConfigListener { 344 /** 345 * Called when the delete subscription request is processed successfully. 346 */ onOppSubscriptionDeleted(int pSIMId)347 void onOppSubscriptionDeleted(int pSIMId); 348 } 349 } 350