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