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