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