You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
13 KiB
327 lines
13 KiB
/*
|
|
* 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);
|
|
}
|
|
}
|