В Android 9 API управления профилями (общедоступные и @SystemApi) доступны через класс EuiccManager. API для связи с eUICC (только @SystemApi) доступны через класс EuiccCardManager.
Что такое eUICC
Операторы могут создавать приложения для управления профилями с помощью EuiccManager, как показано на рисунке 1. Приложения операторов связи не обязательно должны быть системными, но им необходимо предоставить привилегии оператора связи с помощью профилей eUICC. Приложение LPA (LUI и серверная часть LPA) должно быть системным (то есть включенным в системный образ), чтобы вызывать @SystemApi.
Рисунок 1. Телефоны Android с приложением оператора и OEM LPA
Помимо логики вызова EuiccCardManager и взаимодействия с eUICC, приложения LPA должны реализовывать следующее:
- Клиент SM-DP+ взаимодействует с сервером SM-DP+, чтобы пройти аутентификацию и скачать профили.
- [Необязательно] SM-DS, чтобы получить больше потенциальных профилей для скачивания
- Обработка уведомлений для отправки уведомлений на сервер, чтобы обновить статус профиля.
- [Необязательно] Управление слотами, в том числе переключение между логикой eSIM и pSIM. Если в телефоне только чип eSIM, этот шаг можно пропустить.
- eSIM OTA
На телефоне Android может быть несколько приложений LPA, но только одно из них будет работать. Оно выбирается на основе приоритета, заданного в файле AndroidManifest.xml каждого приложения.
Как использовать EuiccManager
API LPA доступны через EuiccManager (в пакете android.telephony.euicc). Приложение оператора может получить экземпляр EuiccManager и вызвать методы в EuiccManager, чтобы получить информацию об eUICC и управлять подписками (в документах GSMA RSP они называются профилями) как экземплярами SubscriptionInfo.
Чтобы вызывать общедоступные API, в том числе для скачивания, переключения и удаления подписки, у приложения оператора должны быть необходимые разрешения. Привилегии оператора мобильной связи добавляются оператором мобильной связи в метаданные профиля. API eUICC применяет правила привилегий оператора соответствующим образом.
Платформа Android не обрабатывает правила политики профиля. Если в метаданных профиля объявлено правило, LPA может выбрать, как обрабатывать процедуру скачивания и установки профиля. Например, сторонний LPA от OEM-производителя может обрабатывать правила с помощью специального кода ошибки (код ошибки передается от LPA от OEM-производителя платформе, а затем платформа передает код LUI от OEM-производителя).
Подробнее о нескольких включенных профилях…
API
Следующие API можно найти в справочной документации EuiccManager и EuiccManager.java.
Получить экземпляр (общедоступный)
Получает экземпляр EuiccManager через Context#getSystemService.
Подробную информацию можно найти в статье getSystemService.
EuiccManager mgr = (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
Проверка включена (общедоступно)
Проверяет, включена ли встроенная подписка. Это нужно проверить, прежде чем обращаться к API LPA. Подробную информацию можно найти в статье isEnabled.
boolean isEnabled = mgr.isEnabled();
if (!isEnabled) {
return;
}
Получить EID (общедоступный)
Получает EID, идентифицирующий оборудование eUICC. Если eUICC не готов, значение может быть нулевым. У звонящего должно быть разрешение оператора связи или разрешение READ_PRIVILEGED_PHONE_STATE. Подробную информацию можно найти в статье getEid.
String eid = mgr.getEid();
if (eid == null) {
// Handle null case.
}
Получение EuiccInfo (общедоступно)
Получает информацию об eUICC. Содержит версию ОС. Подробнее getEuiccInfo…
EuiccInfo info = mgr.getEuiccInfo();
String osVer = info.getOsVersion();
Скачивание подписки (общедоступно)
Скачивает указанную подписку (в документах GSMA RSP она называется профилем). Подписку можно оформить с помощью кода активации. Например, код активации можно получить из QR-кода. Скачивание подписки – это асинхронная операция.
У звонящего должно быть разрешение WRITE_EMBEDDED_SUBSCRIPTIONS или права оператора для целевой подписки. Подробную информацию можно найти в статье downloadSubscription.
// Register receiver.
String action = "download_subscription";
BroadcastReceiver receiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!action.equals(intent.getAction())) {
return;
}
resultCode = getResultCode();
detailedCode = intent.getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
0 /* defaultValue*/);
resultIntent = intent;
}
};
context.registerReceiver(
receiver,
new IntentFilter(action),
"example.broadcast.permission" /* broadcastPermission*/, null /* handler */);
// Download subscription asynchronously.
DownloadableSubscription sub =
DownloadableSubscription.forActivationCode(code /* encodedActivationCode*/);
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.downloadSubscription(sub, true /* switchAfterDownload */, callbackIntent);
Переход на другую подписку (общедоступный)
Переключается на указанную подписку (включает ее). У вызывающего абонента должны быть права доступа WRITE_EMBEDDED_SUBSCRIPTIONS или права оператора для текущей активной подписки и целевой подписки. Подробную информацию можно найти в статье switchToSubscription.
// Register receiver.
String action = "switch_to_subscription";
BroadcastReceiver receiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!action.equals(intent.getAction())) {
return;
}
resultCode = getResultCode();
detailedCode = intent.getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0 /* defaultValue*/);
resultIntent = intent;
}
};
context.registerReceiver(receiver, new IntentFilter(action),
"example.broadcast.permission" /* broadcastPermission*/, null /* handler */);
// Switch to a subscription asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.switchToSubscription(1 /* subscriptionId */, callbackIntent);
Переход на общедоступную подписку с сохранением номера
(Доступно в Android 13 и более поздних версиях.) Переключается на указанную подписку с заданным индексом порта.
У вызывающего абонента должны быть права WRITE_EMBEDDED_SUBSCRIPTIONS или права оператора для текущей активной подписки и целевой подписки.
Подробную информацию можно найти в статье switchToSubscription.
// Register receiver.
String action = "switch_to_subscription";
BroadcastReceiver receiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!action.equals(intent.getAction())) {
return;
}
resultCode = getResultCode();
detailedCode = intent.getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0 /* defaultValue*/);
resultIntent = intent;
}
};
context.registerReceiver(receiver, new IntentFilter(action),
"example.broadcast.permission" /* broadcastPermission*/, null /* handler */);
// Switch to a subscription asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.switchToSubscription(1 /* subscriptionId */, 0 /*portIndex*/, callbackIntent);
Доступен ли порт SIM-карты (общедоступный)
public boolean isSimPortAvailable(int portIndex)
(Доступно в Android 13) Возвращает значение, указывающее, доступен ли переданный индекс порта. Порт доступен, если на нем не оформлена подписка или приложение для звонков имеет права оператора на подписку, установленную на выбранном порту. Подробную информацию можно найти в статье isSimPortAvailable.
Удаление подписки (общедоступное)
Удаляет подписку по ее идентификатору. Если подписка активна, она сначала отключается. У вызывающего абонента должны быть права WRITE_EMBEDDED_SUBSCRIPTIONS или права оператора для целевой подписки. Подробную информацию можно найти в статье deleteSubscription.
// Register receiver.
String action = "delete_subscription";
BroadcastReceiver receiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!action.equals(intent.getAction())) {
return;
}
resultCode = getResultCode();
detailedCode = intent.getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
0 /* defaultValue*/);
resultIntent = intent;
}
};
context.registerReceiver(receiver, new IntentFilter(action),
"example.broadcast.permission" /* broadcastPermission*/,
null /* handler */);
// Delete a subscription asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.deleteSubscription(1 /* subscriptionId */, callbackIntent);
Удаление всех подписок (системный API)
Удаляет все подписки на устройстве. В Android 11 и более поздних версиях нужно указать значение перечисления EuiccCardManager#ResetOption, чтобы определить, какие подписки следует удалить: тестовые, рабочие или и те, и другие. У звонящего должно быть разрешение WRITE_EMBEDDED_SUBSCRIPTIONS.
// Register receiver.
String action = "delete_subscription";
BroadcastReceiver receiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!action.equals(intent.getAction())) {
return;
}
resultCode = getResultCode();
detailedCode = intent.getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
0 /* defaultValue*/);
resultIntent = intent;
}
};
context.registerReceiver(receiver, new IntentFilter(action),
"example.broadcast.permission" /* broadcastPermission*/,
null /* handler */);
// Erase all operational subscriptions asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.eraseSubscriptions(
EuiccCardManager.RESET_OPTION_DELETE_OPERATIONAL_PROFILES, callbackIntent);
Начать устранение проблемы (общедоступно)
Запускает действие для устранения ошибки, которую может исправить пользователь. Если операция возвращает ошибку EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR, этот метод можно вызвать, чтобы предложить пользователю устранить проблему. Этот метод можно вызвать только один раз для определенной ошибки.
...
mgr.startResolutionActivity(getActivity(), 0 /* requestCode */, resultIntent, callbackIntent);
Константы
Чтобы посмотреть список констант public в EuiccManager, перейдите в раздел Константы.