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