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