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.PersistableBundle;
26 import android.telephony.CarrierConfigManager;
27 import android.telephony.SubscriptionInfo;
28 import android.telephony.SubscriptionManager;
29 import android.telephony.euicc.DownloadableSubscription;
30 import android.telephony.euicc.EuiccManager;
31 import android.util.Log;
32 import android.util.Pair;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.util.Stack;
37 
38 public class ONSProfileDownloader {
39 
40     interface IONSProfileDownloaderListener {
onDownloadComplete(int primarySubId)41         void onDownloadComplete(int primarySubId);
onDownloadError(int pSIMSubId, DownloadRetryResultCode resultCode, int detailErrCode)42         void onDownloadError(int pSIMSubId, DownloadRetryResultCode resultCode, int detailErrCode);
43     }
44 
45     private static final String TAG = ONSProfileDownloader.class.getName();
46     public static final String ACTION_ONS_ESIM_DOWNLOAD = "com.android.ons.action.ESIM_DOWNLOAD";
47 
48     @VisibleForTesting protected static final String PARAM_PRIMARY_SUBID = "PrimarySubscriptionID";
49     @VisibleForTesting protected static final String PARAM_REQUEST_TYPE = "REQUEST";
50     @VisibleForTesting protected static final int REQUEST_CODE_DOWNLOAD_SUB = 1;
51 
52     private final Handler mHandler;
53     private final Context mContext;
54     private final CarrierConfigManager mCarrierConfigManager;
55     private final EuiccManager mEuiccManager;
56     private final SubscriptionManager mSubManager;
57     private final ONSProfileConfigurator mONSProfileConfig;
58     private IONSProfileDownloaderListener mListener;
59 
60     // Subscription Id of the CBRS PSIM for which opportunistic eSIM is being downloaded. Used to
61     // ignore duplicate download requests when download is in progress.
62     private int mDownloadingPSimSubId;
63 
64     protected enum DownloadRetryResultCode {
65         DOWNLOAD_SUCCESSFUL,
66         ERR_UNRESOLVABLE,
67         ERR_MEMORY_FULL,
68         ERR_INSTALL_ESIM_PROFILE_FAILED,
69         ERR_RETRY_DOWNLOAD
70     };
71 
ONSProfileDownloader(Context context, CarrierConfigManager carrierConfigManager, EuiccManager euiccManager, SubscriptionManager subManager, ONSProfileConfigurator onsProfileConfigurator, IONSProfileDownloaderListener listener)72     public ONSProfileDownloader(Context context, CarrierConfigManager carrierConfigManager,
73                                 EuiccManager euiccManager, SubscriptionManager subManager,
74                                 ONSProfileConfigurator onsProfileConfigurator,
75                                 IONSProfileDownloaderListener listener) {
76         mContext = context;
77         mListener = listener;
78         mEuiccManager = euiccManager;
79         mSubManager = subManager;
80         mONSProfileConfig = onsProfileConfigurator;
81         mCarrierConfigManager = carrierConfigManager;
82 
83         mHandler = new DownloadHandler();
84     }
85 
86     class DownloadHandler extends Handler {
DownloadHandler()87         DownloadHandler() {
88             super(Looper.myLooper());
89         }
90 
91         @Override
handleMessage(Message msg)92         public void handleMessage(Message msg) {
93             switch (msg.what) {
94                 // Received Response for download request. REQUEST_CODE_DOWNLOAD_SUB was sent to LPA
95                 // as part of request intent.
96                 case REQUEST_CODE_DOWNLOAD_SUB: {
97                     Log.d(TAG, "REQUEST_CODE_DOWNLOAD_SUB callback received");
98 
99                     //Clear downloading subscription flag. Indicates no download in progress.
100                     synchronized (this) {
101                         mDownloadingPSimSubId = -1;
102                     }
103 
104                     int pSIMSubId = ((Intent) msg.obj).getIntExtra(PARAM_PRIMARY_SUBID, 0);
105                     int detailedErrCode = ((Intent) msg.obj).getIntExtra(
106                             EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
107                     int operationCode = ((Intent) msg.obj).getIntExtra(
108                             EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, 0);
109                     int errorCode = ((Intent) msg.obj).getIntExtra(
110                             EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE, 0);
111 
112                     Log.d(TAG, "Result Code : " + detailedErrCode);
113                     Log.d(TAG, "Operation Code : " + operationCode);
114                     Log.d(TAG, "Error Code : " + errorCode);
115 
116                     DownloadRetryResultCode resultCode = mapDownloaderErrorCode(msg.arg1,
117                             detailedErrCode, operationCode, errorCode);
118                     Log.d(TAG, "DownloadRetryResultCode: " + resultCode);
119 
120                     switch (resultCode) {
121                         case DOWNLOAD_SUCCESSFUL:
122                             mListener.onDownloadComplete(pSIMSubId);
123                             break;
124 
125                         case ERR_UNRESOLVABLE:
126                             mListener.onDownloadError(pSIMSubId, resultCode, detailedErrCode);
127                             Log.e(TAG, "Unresolvable download error: "
128                                     + getUnresolvableErrorDescription(errorCode));
129                             break;
130 
131                         default:
132                             mListener.onDownloadError(pSIMSubId, resultCode, detailedErrCode);
133                             break;
134                     }
135                 }
136                 break;
137             }
138         }
139 
140         @VisibleForTesting
mapDownloaderErrorCode(int resultCode, int detailedErrCode, int operationCode, int errorCode)141         protected DownloadRetryResultCode mapDownloaderErrorCode(int resultCode,
142                                                                     int detailedErrCode,
143                                                                     int operationCode,
144                                                                     int errorCode) {
145 
146             if (operationCode == EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE) {
147                 //SMDP Error codes handling
148                 Pair<String, String> errCode = decodeSmdxSubjectAndReasonCode(detailedErrCode);
149                 Log.e(TAG, " Subject Code: " + errCode.first + " Reason Code: "
150                         + errCode.second);
151 
152                 //8.1 - eUICC, 4.8 - Insufficient Memory
153                 // eUICC does not have sufficient space for this Profile.
154                 if (errCode.equals(Pair.create("8.1.0", "4.8"))) {
155                     return DownloadRetryResultCode.ERR_MEMORY_FULL;
156                 }
157 
158                 //8.8.5 - Download order, 4.10 - Time to Live Expired
159                 //The Download order has expired
160                 if (errCode.equals(Pair.create("8.8.5", "4.10"))) {
161                     return DownloadRetryResultCode.ERR_RETRY_DOWNLOAD;
162                 }
163 
164                 //All other errors are unresolvable or retry after SIM State Change
165                 return DownloadRetryResultCode.ERR_UNRESOLVABLE;
166 
167             }
168 
169             switch (errorCode) {
170 
171                 //Success Cases
172                 case EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK: {
173                     return DownloadRetryResultCode.DOWNLOAD_SUCCESSFUL;
174                 }
175 
176                 //Low eUICC memory cases
177                 case EuiccManager.ERROR_EUICC_INSUFFICIENT_MEMORY: {
178                     Log.d(TAG, "Download ERR: EUICC_INSUFFICIENT_MEMORY");
179                     return DownloadRetryResultCode.ERR_MEMORY_FULL;
180                 }
181 
182                 //Temporary download error cases
183                 case EuiccManager.ERROR_TIME_OUT:
184                 case EuiccManager.ERROR_CONNECTION_ERROR:
185                 case EuiccManager.ERROR_OPERATION_BUSY: {
186                     return DownloadRetryResultCode.ERR_RETRY_DOWNLOAD;
187                 }
188 
189                 //Profile installation failure cases
190                 case EuiccManager.ERROR_INSTALL_PROFILE: {
191                     return DownloadRetryResultCode.ERR_INSTALL_ESIM_PROFILE_FAILED;
192                 }
193 
194                 default: {
195                     return DownloadRetryResultCode.ERR_UNRESOLVABLE;
196                 }
197             }
198         }
199     }
200 
getUnresolvableErrorDescription(int errorCode)201     private String getUnresolvableErrorDescription(int errorCode) {
202         switch (errorCode) {
203             case EuiccManager.ERROR_INVALID_ACTIVATION_CODE:
204                 return "ERROR_INVALID_ACTIVATION_CODE";
205 
206             case EuiccManager.ERROR_UNSUPPORTED_VERSION:
207                 return "ERROR_UNSUPPORTED_VERSION";
208 
209             case EuiccManager.ERROR_INSTALL_PROFILE:
210                 return "ERROR_INSTALL_PROFILE";
211 
212             case EuiccManager.ERROR_SIM_MISSING:
213                 return "ERROR_SIM_MISSING";
214 
215             case EuiccManager.ERROR_ADDRESS_MISSING:
216                 return "ERROR_ADDRESS_MISSING";
217 
218             case EuiccManager.ERROR_CERTIFICATE_ERROR:
219                 return "ERROR_CERTIFICATE_ERROR";
220 
221             case EuiccManager.ERROR_NO_PROFILES_AVAILABLE:
222                 return "ERROR_NO_PROFILES_AVAILABLE";
223 
224             case EuiccManager.ERROR_CARRIER_LOCKED:
225                 return "ERROR_CARRIER_LOCKED";
226         }
227 
228         return "Unknown";
229     }
230 
231     protected enum DownloadProfileResult {
232         SUCCESS,
233         DUPLICATE_REQUEST,
234         INVALID_SMDP_ADDRESS
235     }
236 
downloadProfile(int primarySubId)237     protected DownloadProfileResult downloadProfile(int primarySubId) {
238         Log.d(TAG, "downloadProfile");
239 
240         //Get SMDP address from carrier configuration.
241         String smdpAddress = getSMDPServerAddress(primarySubId);
242         if (smdpAddress == null || smdpAddress.length() <= 0) {
243             return DownloadProfileResult.INVALID_SMDP_ADDRESS;
244         }
245 
246         synchronized (this) {
247             if (mDownloadingPSimSubId == primarySubId) {
248                 Log.d(TAG, "Download already in progress.");
249                 return DownloadProfileResult.DUPLICATE_REQUEST;
250             }
251 
252             mDownloadingPSimSubId = primarySubId;
253         }
254 
255         //Generate Activation code 1${SM-DP+ FQDN}$
256         String activationCode = "1$" + smdpAddress + "$";
257         Intent intent = new Intent(mContext, ONSProfileResultReceiver.class);
258         intent.setAction(ACTION_ONS_ESIM_DOWNLOAD);
259         intent.putExtra(PARAM_REQUEST_TYPE, REQUEST_CODE_DOWNLOAD_SUB);
260         intent.putExtra(PARAM_PRIMARY_SUBID, primarySubId);
261         PendingIntent callbackIntent = PendingIntent.getBroadcast(mContext,
262                 REQUEST_CODE_DOWNLOAD_SUB, intent, PendingIntent.FLAG_MUTABLE);
263 
264         Log.d(TAG, "Download Request sent to EUICC Manager");
265         // TODO: Check with euicc team if card specific EUICC Manager should be used to download.
266         // Once the eSIM profile is downloaded, port is auto decided by eUICC to activate the
267         // subscription. If there is no available port, an
268         // {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback
269         // intent to prompt the user to disable an already-active subscription. However, ONS will
270         // not show any prompt to the user and silently fails to activate the subscription.
271         // ONS will try to provision again when carrier configuration change event is received.
272         SubscriptionInfo primarySubInfo = mSubManager.getActiveSubscriptionInfo(primarySubId);
273         if (primarySubInfo != null && primarySubInfo.isEmbedded()) {
274             EuiccManager euiccManager = mEuiccManager.createForCardId(primarySubInfo.getCardId());
275             euiccManager.downloadSubscription(DownloadableSubscription.forActivationCode(
276                     activationCode), true, callbackIntent);
277         } else {
278             mEuiccManager.downloadSubscription(DownloadableSubscription.forActivationCode(
279                     activationCode), true, callbackIntent);
280         }
281 
282         return DownloadProfileResult.SUCCESS;
283     }
284 
285     /**
286      * Retrieves SMDP+ server address of the given subscription from carrier configuration.
287      *
288      * @param subscriptionId subscription Id of the primary SIM.
289      * @return FQDN of SMDP+ server.
290      */
getSMDPServerAddress(int subscriptionId)291     private String getSMDPServerAddress(int subscriptionId) {
292         PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subscriptionId);
293         if (config != null) {
294             return config.getString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING);
295         } else {
296             return null;
297         }
298     }
299 
300     /**
301      * Given encoded error code described in
302      * {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE} decode it
303      * into SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2)
304      *
305      * @param resultCode from
306      *               {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE}
307      *
308      * @return a pair containing SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22
309      * v2.2)
310      */
311     @VisibleForTesting
decodeSmdxSubjectAndReasonCode(int resultCode)312     protected static Pair<String, String> decodeSmdxSubjectAndReasonCode(int resultCode) {
313         final int numOfSections = 6;
314         final int bitsPerSection = 4;
315         final int sectionMask = 0xF;
316 
317         final Stack<Integer> sections = new Stack<>();
318 
319         // Extracting each section of digits backwards.
320         for (int i = 0; i < numOfSections; ++i) {
321             int sectionDigit = resultCode & sectionMask;
322             sections.push(sectionDigit);
323             resultCode = resultCode >>> bitsPerSection;
324         }
325 
326         String subjectCode = sections.pop() + "." + sections.pop() + "." + sections.pop();
327         String reasonCode = sections.pop() + "." + sections.pop() + "." + sections.pop();
328 
329         // drop the leading zeros, e.g. 0.1 -> 1, 0.0.3 -> 3, 0.5.1 -> 5.1
330         subjectCode = subjectCode.replaceAll("^(0\\.)*", "");
331         reasonCode = reasonCode.replaceAll("^(0\\.)*", "");
332 
333         return Pair.create(subjectCode, reasonCode);
334     }
335 
336     /**
337      * Callback to receive result for subscription activate request and process the same.
338      * @param intent
339      * @param resultCode
340      */
onCallbackIntentReceived(Intent intent, int resultCode)341     public void onCallbackIntentReceived(Intent intent, int resultCode) {
342         Message msg = new Message();
343         msg.what = REQUEST_CODE_DOWNLOAD_SUB;
344         msg.arg1 = resultCode;
345         msg.obj = intent;
346         mHandler.sendMessage(msg);
347     }
348 }
349