blob: 11c6237b973b5a102a12ce63be8645f21aac8d24 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ons;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Stack;
public class ONSProfileDownloader {
interface IONSProfileDownloaderListener {
void onDownloadComplete(int primarySubId);
void onDownloadError(int pSIMSubId, DownloadRetryResultCode resultCode, int detailErrCode);
}
private static final String TAG = ONSProfileDownloader.class.getName();
public static final String ACTION_ONS_ESIM_DOWNLOAD = "com.android.ons.action.ESIM_DOWNLOAD";
@VisibleForTesting protected static final String PARAM_PRIMARY_SUBID = "PrimarySubscriptionID";
@VisibleForTesting protected static final String PARAM_REQUEST_TYPE = "REQUEST";
@VisibleForTesting protected static final int REQUEST_CODE_DOWNLOAD_SUB = 1;
private final Handler mHandler;
private final Context mContext;
private final CarrierConfigManager mCarrierConfigManager;
private final EuiccManager mEuiccManager;
private final ONSProfileConfigurator mONSProfileConfig;
private IONSProfileDownloaderListener mListener;
// Subscription Id of the CBRS PSIM for which opportunistic eSIM is being downloaded. Used to
// ignore duplicate download requests when download is in progress.
private int mDownloadingPSimSubId;
protected enum DownloadRetryResultCode {
DOWNLOAD_SUCCESSFUL,
ERR_UNRESOLVABLE,
ERR_MEMORY_FULL,
ERR_INSTALL_ESIM_PROFILE_FAILED,
ERR_RETRY_DOWNLOAD
};
public ONSProfileDownloader(Context context, CarrierConfigManager carrierConfigManager,
EuiccManager euiccManager,
ONSProfileConfigurator onsProfileConfigurator,
IONSProfileDownloaderListener listener) {
mContext = context;
mListener = listener;
mEuiccManager = euiccManager;
mONSProfileConfig = onsProfileConfigurator;
mCarrierConfigManager = carrierConfigManager;
mHandler = new DownloadHandler();
}
class DownloadHandler extends Handler {
DownloadHandler() {
super(Looper.myLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// Received Response for download request. REQUEST_CODE_DOWNLOAD_SUB was sent to LPA
// as part of request intent.
case REQUEST_CODE_DOWNLOAD_SUB: {
Log.d(TAG, "REQUEST_CODE_DOWNLOAD_SUB callback received");
//Clear downloading subscription flag. Indicates no download in progress.
synchronized (this) {
mDownloadingPSimSubId = -1;
}
int pSIMSubId = ((Intent) msg.obj).getIntExtra(PARAM_PRIMARY_SUBID, 0);
int detailedErrCode = ((Intent) msg.obj).getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
int operationCode = ((Intent) msg.obj).getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, 0);
int errorCode = ((Intent) msg.obj).getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE, 0);
Log.d(TAG, "Result Code : " + detailedErrCode);
Log.d(TAG, "Operation Code : " + operationCode);
Log.d(TAG, "Error Code : " + errorCode);
DownloadRetryResultCode resultCode = mapDownloaderErrorCode(msg.arg1,
detailedErrCode, operationCode, errorCode);
Log.d(TAG, "DownloadRetryResultCode: " + resultCode);
switch (resultCode) {
case DOWNLOAD_SUCCESSFUL:
mListener.onDownloadComplete(pSIMSubId);
break;
case ERR_UNRESOLVABLE:
mListener.onDownloadError(pSIMSubId, resultCode, detailedErrCode);
Log.e(TAG, "Unresolvable download error: "
+ getUnresolvableErrorDescription(errorCode));
break;
default:
mListener.onDownloadError(pSIMSubId, resultCode, detailedErrCode);
break;
}
}
break;
}
}
@VisibleForTesting
protected DownloadRetryResultCode mapDownloaderErrorCode(int resultCode,
int detailedErrCode,
int operationCode,
int errorCode) {
if (operationCode == EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE) {
//SMDP Error codes handling
Pair<String, String> errCode = decodeSmdxSubjectAndReasonCode(detailedErrCode);
Log.e(TAG, " Subject Code: " + errCode.first + " Reason Code: "
+ errCode.second);
//8.1 - eUICC, 4.8 - Insufficient Memory
// eUICC does not have sufficient space for this Profile.
if (errCode.equals(Pair.create("8.1.0", "4.8"))) {
return DownloadRetryResultCode.ERR_MEMORY_FULL;
}
//8.8.5 - Download order, 4.10 - Time to Live Expired
//The Download order has expired
if (errCode.equals(Pair.create("8.8.5", "4.10"))) {
return DownloadRetryResultCode.ERR_RETRY_DOWNLOAD;
}
//All other errors are unresolvable or retry after SIM State Change
return DownloadRetryResultCode.ERR_UNRESOLVABLE;
}
switch (errorCode) {
//Success Cases
case EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK: {
return DownloadRetryResultCode.DOWNLOAD_SUCCESSFUL;
}
//Low eUICC memory cases
case EuiccManager.ERROR_EUICC_INSUFFICIENT_MEMORY: {
Log.d(TAG, "Download ERR: EUICC_INSUFFICIENT_MEMORY");
return DownloadRetryResultCode.ERR_MEMORY_FULL;
}
//Temporary download error cases
case EuiccManager.ERROR_TIME_OUT:
case EuiccManager.ERROR_CONNECTION_ERROR:
case EuiccManager.ERROR_OPERATION_BUSY: {
return DownloadRetryResultCode.ERR_RETRY_DOWNLOAD;
}
//Profile installation failure cases
case EuiccManager.ERROR_INSTALL_PROFILE: {
return DownloadRetryResultCode.ERR_INSTALL_ESIM_PROFILE_FAILED;
}
default: {
return DownloadRetryResultCode.ERR_UNRESOLVABLE;
}
}
}
}
private String getUnresolvableErrorDescription(int errorCode) {
switch (errorCode) {
case EuiccManager.ERROR_INVALID_ACTIVATION_CODE:
return "ERROR_INVALID_ACTIVATION_CODE";
case EuiccManager.ERROR_UNSUPPORTED_VERSION:
return "ERROR_UNSUPPORTED_VERSION";
case EuiccManager.ERROR_INSTALL_PROFILE:
return "ERROR_INSTALL_PROFILE";
case EuiccManager.ERROR_SIM_MISSING:
return "ERROR_SIM_MISSING";
case EuiccManager.ERROR_ADDRESS_MISSING:
return "ERROR_ADDRESS_MISSING";
case EuiccManager.ERROR_CERTIFICATE_ERROR:
return "ERROR_CERTIFICATE_ERROR";
case EuiccManager.ERROR_NO_PROFILES_AVAILABLE:
return "ERROR_NO_PROFILES_AVAILABLE";
case EuiccManager.ERROR_CARRIER_LOCKED:
return "ERROR_CARRIER_LOCKED";
}
return "Unknown";
}
protected enum DownloadProfileResult {
SUCCESS,
DUPLICATE_REQUEST,
INVALID_SMDP_ADDRESS
}
protected DownloadProfileResult downloadProfile(int primarySubId) {
Log.d(TAG, "downloadProfile");
//Get SMDP address from carrier configuration.
String smdpAddress = getSMDPServerAddress(primarySubId);
if (smdpAddress == null || smdpAddress.length() <= 0) {
return DownloadProfileResult.INVALID_SMDP_ADDRESS;
}
synchronized (this) {
if (mDownloadingPSimSubId == primarySubId) {
Log.d(TAG, "Download already in progress.");
return DownloadProfileResult.DUPLICATE_REQUEST;
}
mDownloadingPSimSubId = primarySubId;
}
//Generate Activation code 1${SM-DP+ FQDN}$
String activationCode = "1$" + smdpAddress + "$";
Intent intent = new Intent(mContext, ONSProfileResultReceiver.class);
intent.setAction(ACTION_ONS_ESIM_DOWNLOAD);
intent.putExtra(PARAM_REQUEST_TYPE, REQUEST_CODE_DOWNLOAD_SUB);
intent.putExtra(PARAM_PRIMARY_SUBID, primarySubId);
PendingIntent callbackIntent = PendingIntent.getBroadcast(mContext,
REQUEST_CODE_DOWNLOAD_SUB, intent, PendingIntent.FLAG_MUTABLE);
Log.d(TAG, "Download Request sent to EUICC Manager");
mEuiccManager.downloadSubscription(DownloadableSubscription.forActivationCode(
activationCode), true, callbackIntent);
return DownloadProfileResult.SUCCESS;
}
/**
* Retrieves SMDP+ server address of the given subscription from carrier configuration.
*
* @param subscriptionId subscription Id of the primary SIM.
* @return FQDN of SMDP+ server.
*/
private String getSMDPServerAddress(int subscriptionId) {
PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subscriptionId);
return config.getString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING);
}
/**
* Given encoded error code described in
* {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE} decode it
* into SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2)
*
* @param resultCode from
* {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE}
*
* @return a pair containing SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22
* v2.2)
*/
@VisibleForTesting
protected static Pair<String, String> decodeSmdxSubjectAndReasonCode(int resultCode) {
final int numOfSections = 6;
final int bitsPerSection = 4;
final int sectionMask = 0xF;
final Stack<Integer> sections = new Stack<>();
// Extracting each section of digits backwards.
for (int i = 0; i < numOfSections; ++i) {
int sectionDigit = resultCode & sectionMask;
sections.push(sectionDigit);
resultCode = resultCode >>> bitsPerSection;
}
String subjectCode = sections.pop() + "." + sections.pop() + "." + sections.pop();
String reasonCode = sections.pop() + "." + sections.pop() + "." + sections.pop();
// drop the leading zeros, e.g. 0.1 -> 1, 0.0.3 -> 3, 0.5.1 -> 5.1
subjectCode = subjectCode.replaceAll("^(0\\.)*", "");
reasonCode = reasonCode.replaceAll("^(0\\.)*", "");
return Pair.create(subjectCode, reasonCode);
}
/**
* Callback to receive result for subscription activate request and process the same.
* @param intent
* @param resultCode
*/
public void onCallbackIntentReceived(Intent intent, int resultCode) {
Message msg = new Message();
msg.what = REQUEST_CODE_DOWNLOAD_SUB;
msg.arg1 = resultCode;
msg.obj = intent;
mHandler.sendMessage(msg);
}
}